Listy, stosy, kolejki

Transkrypt

Listy, stosy, kolejki
Marcin Matusiak i Łukasz Stasiak


Lista jest sekwencyjną strukturą danych, która składa się z ciągu elementów
tego samego typu. Dostęp do elementów listy jest sekwencyjny – tzn. z
danego elementu listy możemy przejść do elementu następnego lub do
poprzedniego. Dojście do elementu i-tego wymaga przejścia przez kolejne
elementy od pierwszego do docelowego. Takie zachowanie się tej struktury
jest konsekwencją budowy jej elementów. Oprócz samych danych każdy
element listy przechowuje zwykle dwa wskaźniki – do elementu następnego
listy oraz do elementu poprzedzającego na liście. Nazwijmy te wskaźniki
odpowiednio (stosujemy nazwy angielskie z powodu ich powszechności w
literaturze):
next – wskazuje kolejny element,
prev – wskazuje poprzedni element
W przeciwieństwie do tablicy elementy listy nie muszą leżeć obok siebie w
pamięci. Zatem lista nie wymaga ciągłego obszaru pamięci i może być
rozłożona w różnych jej segmentach – w porównaniu do tablic jest to
niewątpliwie zaletą.
Po tak określonej liście możemy się poruszać w obu kierunkach, które
wyznaczają pola next i prev. Listę o takiej własności nazywamy listą
dwukierunkową . Pierwszy element listy nie posiada poprzednika, zatem w
polu prev przechowuje wskaźnik pusty (czyli zero – adres zero nie wskazuje
żadnego elementu). Podobnie ostatni element listy nie posiada następnika i
jego pole next zawiera wskaźnik pusty.
W niektórych zastosowaniach nie potrzebujemy przechodzić listy w obu
kierunkach. W takim przypadku w każdym elemencie wystarczy jeden
wskaźnik, np. next:
Taką uproszczoną listę nazywamy listą jednokierunkową
Jeśli za następnik ostatniego elementu listy przyjmiemy pierwszy element na
liście, to otrzymamy jednokierunkową listę cykliczną
Lista cykliczna nigdy się nie kończy – przechodzimy ją cyklicznie w koło.
Jeśli dodatkowo poprzednikiem pierwszego elementu listy zrobimy ostatni
element, to otrzymamy dwukierunkową listę cykliczną .
Taką listę cykliczną możemy przechodzić w obu kierunkach.
W tablicy dostęp do wybranego elementu jest bardzo szybki, ponieważ
elementy znajdują się jeden obok drugiego w ciągłym bloku pamięci (aby
obliczyć adres elementu o danym indeksie wystarczy do adresu tablicy dodać
iloczyn indeksu przez rozmiar elementu). W przypadku listy nie możemy tak
postąpić, ponieważ elementy mogą znajdować się w dowolnym obszarze
pamięci i niekoniecznie obok siebie. Nawet jeśli znajdują się obok siebie, to
ich kolejność na liście wcale nie musi odpowiadać kolejności położenia w
pamięci. W przypadku listy musimy przechodzić kolejno od jednego
elementu do drugiego wzdłuż ścieżki wyznaczonej przez ich pola next lub
prev. Przy długiej liście może to być operacja czasochłonna. To z kolei jest
wadą list w porównaniu z tablicami.
Tworząc listę w pamięci zwykle dodatkowo rezerwuje się trzy zmienne dla jej
obsługi:
wskaźnik head – wskazuje pierwszy element listy (ang. head = głowa)
wskaźnik tail – wskazuje ostatni element listy (ang. tail = ogon)
licznik count – zlicza elementy na liście
Zmienna tego typu umożliwia dostęp do początku i do końca listy oraz
zapamiętuje liczbę elementów przechowywanych na liście. Można ją
stosować zarówno do list jednokierunkowych jak i dwukierunkowych. Jeśli
interesuje nas jedynie dostęp do początku listy, to można pominąć składniki
tail i count. W dalszej części artykułu podajemy podstawowe algorytmy
operujące na listach. Przyjęliśmy zasadę, iż listy jednokierunkowe będą
maksymalnie uproszczone – do obsługi listy będzie stosowana tylko zmienna
head, a listy dwukierunkowe będą posiadały wszystkie ułatwienia.
Stos jest sekwencyjną strukturą danych.




Stos jest taką strukturą danych, z której odczytujemy elementy w kolejności
odwrotnej do ich wstawiania. Struktura ta nosi nazwę LIFO.
Rozróżniamy następujące operacje dla stosu:
Sprawdzenie, czy stos jest pusty – operacja empty zwraca true, jeśli stos nie
zawiera żadnego elementu, w przeciwnym razie zwraca false
Odczyt szczytu stosu – operacja top zwraca element (zwykle jest to
wskaźnik) znajdujący się na szczycie stosu, sam element pozostaje wciąż na
stosie.
Zapis na stos – operacja push umieszcza na szczycie stosu nowy element.
Usunięcie ze stosu – operacja pop usuwa ze szczytu stosu znajdujący się tam
element.
Stosy możemy realizować za pomocą tablic lub list jednokierunkowych.
Realizacja tablicowa jest bardzo prosta i szybka. Stosujemy ją wtedy, gdy
dokładnie wiemy, ile maksymalnie elementów będzie przechowywał stos –
jest to potrzebne do przygotowania odpowiednio pojemnej tablicy na
elementy stosu. Realizacja za pomocą listy jednokierunkowej jest przydatna
wtedy, gdy nie znamy dokładnego rozmiaru stosu – listy dostosowują się
dobrze do obszarów wolnej pamięci.
Do utworzenia stosu w tablicy potrzebujemy dwóch zmiennych. Pierwszą z
nich będzie tablica, która przechowuje umieszczone na stosie elementy.
Druga zmienna sptr służy do zapamiętywania pozycji szczytu stosu i nosi
nazwę wskaźnika stosu . Umawiamy się, że wskaźnik stosu zawsze wskazuje
pustą komórkę tablicy, która znajduje się tuż ponad szczytem stosu:
Po utworzeniu tablicy zmienna sptr musi zawsze być zerowana. Stos jest
pusty, gdy sptr wskazuje początek tablicy, czyli komórkę o indeksie zero. Ta
własność jest wykorzystywana w operacji empty. Stos jest pełny, gdy sptr ma
wartość równą liczbie komórek tablicy. W takim przypadku na stosie nie
można już umieszczać żadnych dalszych danych. gdyż trafiłyby poza obszar
zarezerwowany na tablicę.
Wejście:
sptr – zmienna przechowująca
wskaźnik stosu tablicy
Wyjście:
True, jeśli na stosie nie ma żadnego
elementu, inaczej false
Lista kroków:
K01 Jeśli sptr = 0, to zakończ z
wynikiem true
K02: Zakończ z wynikiem false
C++
bool empty(void) { return !sptr; }
Wejście:
sptr – zmienna przechowująca
wskaźnik stosu tablicy n – rozmiar
tablicy S – tablica przechowująca
stos
Wyjście:
Zawartość szczytu stosu lub wartość
specjalna, jeśli stos jest pusty.
Lista kroków:
K01 Jeśli sptr = 0, to zakończ z
wynikiem wartość specjalną
K02: Zakończ z wynikiem S[sptr - 1]
C++
typ_danych top(void) { if(sptr) return
S[sptr - 1); return
wartość_specjalna }
Wejście
sptr – zmienna przechowująca
wskaźnik stosu tablicy n – rozmiar
tablicy S – tablica przechowująca
stos v – zapisywana wartość
Wyjście:
Na stosie zostaje zapisana wartość v,
jeśli jest na to miejsce. W
przeciwnym razie v nie będzie
zapisane.
Lista kroków:
K01 Jeśli sptr = n, to zakończ ; stos
jest pełny i nie ma miejsca na
nową wartość
K02: S[sptr] ← v ; umieszczamy v
ponad szczytem stosu K03: sptr ←
sptr + 1 ; zwiększamy wskaźnik
stosu K04: Zakończ


C++
void push(typ_danych v) { if(sptr <
n) S[sptr++] = v; }
Wejście
sptr – zmienna przechowująca
wskaźnik stosu tablicy S – tablica
przechowująca stos Wyjście:
Ze szczytu stosu zostaje usunięty
element.
Lista kroków:
K01 Jeśli sptr > 0, to sptr ← sptr - 1 ;
jeśli stos coś zawiera, to usuwamy
element na szczycie stosu
K02: Zakończ
C++
void pop(void) { if(sptr) sptr--; }
Poniższy program przedstawia sposób implementacji stosu w tablicy. Tworzy
on obiekt zawierający tablicę liczb całkowitych, wskaźnik stosu oraz metody
obsługi tej struktury. Rozmiar tablicy jest określany przy tworzeniu obiektu.
Na stosie zostaje zapisanych 10 liczb, a następnie są one odczytywane i
wyświetlane.




// Stos w tablicy
// Data: 13.08.2012
// (C)2012 mgr Jerzy Wałaszek
//------------------------------





#include <iostream>



using namespace std;


const int MAXINT = -2147483647;

















// Definicja typu obiektowego stack
//-------------------------------class stack
{
private:
int n;
// rozmiar tablicy
int sptr; // wskaźnik stosu
int * S; // tablica dynamiczna
public:
stack(int x); // konstruktor
~stack();
// destruktor
bool empty(void);
int top(void);
void push(int v);
void pop(void);
};

//--------------------// Metody obiektu stack
//---------------------

// Konstruktor - tworzy tablicę dla stosu







//-------------------------------------stack::stack(int x)
{
n = x;
S = new int[x];
sptr = 0;
}
// Destruktor - zwalnia tablicę
dynamiczną
//--------------------------------------stack::~stack()
{
delete [] S;
}






























// Sprawdza, czy stos jest pusty
//-----------------------------bool stack::empty(void)
{
return !sptr;
}
// Zwraca szczyt stosu.
// Wartość specjalna to -MAXINT
//----------------------------int stack::top(void)
{
if(sptr) return S[sptr - 1];
return -MAXINT;
}
// Zapisuje na stos
//-----------------



void stack::push(int v)
{
if(sptr < n) S[sptr++] = v;
}
// Usuwa ze stosu
//--------------void stack::pop(void)
{
if(sptr) sptr--;
}
//--------------// Program główny
//--------------int main()
{
stack S(10); // tworzymy stos na 10
elementów
int i;
for(i = 1; i <= 10; i++) S.push(i);

while(!S.empty())
{
cout << S.top() << endl;
S.pop();
}






}
Do realizacji stosu możemy w prosty sposób wykorzystać listę
jednokierunkową. Zapis na stos będzie wtedy polegał na umieszczaniu
elementu na początku listy. Szczyt stosu będzie pierwszym elementem listy.
Odczyt ze stosu będzie równoważny odczytowi pierwszego elementu listy, a
usunięcie ze stosu będzie odpowiadało usunięciu elementu z początku listy.
Realizacja listowa jest szczególnie wygodna wtedy, gdy nie znamy
maksymalnego rozmiaru stosu – w przeciwieństwie do tablic listy mogą
swobodnie rosnąć w pamięci, dopóki jest dla nich miejsce. W podanych niżej
procedurach nie obsługujemy sytuacji braku pamięci – w każdym ze
środowisk programowania można w takim przypadku wykorzystać
mechanizmy wyłapywania błędów, które jednakże zaciemniają realizowane
funkcje.
Każdy element listy jest następującą strukturą danych:
struct slistEl { slistEl * next; typ_danych data; };
Do obsługi listy potrzebujemy wskaźnika, który wskazuje jej początek.
Zdefiniujmy go następująco:
...
slistEl * stack;
...
Przed pierwszym użyciem wskaźnik stack musi być odpowiednio wyzerowany:
...
stack = NULL;
...
Wejście
p – wskaźnik szczytu stosu
Wyjście:
True, jeśli na stosie nie ma żadnego
elementu, inaczej false
Lista kroków:
K01 Jeśli p = nil, to zakończ z
wynikiem true
K02: Zakończ z wynikiem false
C++
bool empty(slistEl * p) { return !p;
}
Wejście
p – wskaźnik szczytu stosu Wyjście:
Zwraca wskazanie elementu, który
jest bieżącym szczytem stosu lub
nil, jeśli stos jest pusty
Lista kroków:
K01: Zakończ z wynikiem p
C++
slist * top(slistEl * p)
{ return p; }
Wejście
p – wskaźnik szczytu stosu
v – zapisywana wartość
Wyjście:
Na stosie zostaje zapisana wartość v,
jeśli jest na to miejsce. Inaczej nic
nie zostaje zapisane.
Dane pomocnicze:
e – wskaźnik elementu listy
Lista kroków:
K01 Utwórz element listy i umieść jego
adres w e
K02: e→data ← v ; dane umieszczamy
w polu data
K03: e→next ← p ; następnikiem będzie
bieżący szczyt stosu
K04: p ← e ; szczytem stosu staje się
dodany element
K05: Zakończ
C++
void push(slistEl * & p, typ_danych v)
{ slistEl * e = new slistEl; e->data
= v; e->next = p; p = e; }
Wejście
p – wskaźnik szczytu stosu Wyjście:
Ze szczytu stosu zostaje usunięty
element.
Dane pomocnicze:
e – wskaźnik elementu listy
Lista kroków:
K01 Jeśli p = nil, to zakończ ; stos
jest pusty
K02: e ← p ; zapamiętujemy szczyt
stosu
K03 p ← p→next ; usuwamy ze stosu
bieżący szczyt
K04: Usuń z pamięci element
wskazany przez e
K05: Zakończ


C++
void pop(slistEl * & p) { if(p) { slistEl
* e = p; p = p->next; delete e; } }
Poniższy program przedstawia sposób implementacji stosu za pomocą listy
jednokierunkowej. Tworzy on obiekt zawierający pustą listę liczb całkowitych
oraz metody obsługi tej struktury. Na stosie zostaje zapisanych 10 liczb, a
następnie są one odczytywane i wyświetlane.

// Stos na liście jednokierunkowej

// Data: 13.08.2012

// (C)2012 mgr Jerzy Wałaszek

stack::stack()


//------------------------------

{

}

#include <iostream>

// Usuwa ze stosu

//---------------

void stack::pop(void)

{

//------------

using namespace std;

// Definicja typu obiektowego stack

//---------------------------------
//----------------------------------------

stack::~stack()

if(S)

{

{

{

int data;
}



S = e;
// Destruktor - zwalnia tablicę dynamiczną
struct slistEl
slistEl * next;
e->next = S;



e->data = v;

S = NULL;



while(S) pop();
}

slistEl * e = S;

S = S->next;
delete e;


// Sprawdza, czy stos jest pusty

}

};

//------------------------------

}

class stack

bool stack::empty(void)

//---------------

{

{

// Program główny

//---------------

private:
slistEl * S; // lista przechowująca stos


return !S;

public:
}

int main()

// Zwraca szczyt stosu

{

//--------------------

stack S;

int i;

for(i = 1; i <= 10; i++) S.push(i);

while(!S.empty())

{

stack();

~stack();

bool empty(void);

slistEl * stack::top(void)

slistEl * top(void);

{

void push(int v);

void pop(void);



// konstruktor

// destruktor
}
};

//---------------------

// Metody obiektu stack

//---------------------

return S;
// Konstruktor

// Zapisuje na stos


//-----------------

cout << S.top()->data << endl;
S.pop();
}


void stack::push(int v)

{

slistEl * e = new slistEl;

}
Kolejka jest sekwencyjną strukturą danych o takiej własności, iż element
zapisany jako pierwszy jest również odczytywany jako pierwszy. Taka
struktura w literaturze informatycznej nosi nazwę FIFO. Kolejkę możemy
sobie wyobrazić jako tubę – elementy wstawiamy do tuby z jednej strony, po
czym przesuwają się one wewnątrz i wychodzą z drugiej strony w tej samej
kolejności, w jakiej zostały do tuby włożone.
Dla kolejki są zdefiniowane operacje:
Sprawdzenie, czy kolejka jest pusta – operacja empty zwraca true, jeśli kolejka
nie zawiera żadnego elementu, w przeciwnym razie zwraca false.
Odczyt elementu z początku kolejki – operacja front zwraca wskazanie do
elementu, który jest pierwszy w kolejce.
Zapis elementu na koniec kolejki – operacja push dopisuje nowy element na
koniec elementów przechowywanych w kolejce.
Usunięcie elementu z kolejki – operacja pop usuwa z kolejki pierwszy element.
Jeśli porównasz te operacje z operacjami dostępnymi dla stosu, to okaże się, że
obie te struktury są bardzo do siebie podobne. Różnią się jedynie kolejnością
dostępu do elementów.
Naturalną strukturą danych dla kolejek jest lista, jednakże w prostszych przypadkach kolejkę da
się zrealizować w tablicy. W takim przypadku musimy założyć z góry maksymalny rozmiar
kolejki (ilość przechowywanych w niej elementów). Będziemy potrzebowali trzech zmiennych:
Q – tablica, w której będzie tworzona kolejka. Tablica ma n elementów, indeksy rozpoczynają się
od 0.
qptr – indeks elementu, który jest początkiem kolejki
qcnt – liczba elementów, którą przechowuje kolejka
Kolejkę tworzą kolejne elementy o indeksach rozpoczynających się od qptr. Na początku qptr
wskazuje pierwszy element tablicy o indeksie 0:
Koniec kolejki wyznacza indeks równy qptr + qcnt. Jeśli indeks ten wykracza poza ostatni element
tablicy, to należy go zmniejszyć o n. Dopisanie elementu zwiększa licznik qcnt o 1:
Odczyt elementu z kolejki polega na przetworzeniu elementu o indeksie qptr. Usunięcie elementu
z kolejki polega na zwiększeniu o 1 indeksu qptr. Jeśli po zwiększeniu qptr wskazuje poza
ostatni element tablicy, to qptr należy wyzerować – kolejka znów rozpocznie się od początku
tablicy. Licznik qptr zawsze zmniejszamy o 1.
Zwróć uwagę, że w tej implementacji kolejka nie zajmuje stałego położenia w tablicy. Co więcej,
jej elementy mogą być rozdzielone:
Wejście
qcnt – liczba elementów
przechowywana w kolejce
Wyjście:
True, jeśli kolejka jest pusta, inaczej
false
Lista kroków:
K01 Jeśli qcnt = 0, to zakończ z
wynikiem true
K02: Zakończ z wynikiem false
C++
bool empty(void) { return !qcnt; }
Wejście
Q – tablica, w której przechowywana
jest kolejka qcnt – liczba
elementów przechowywana w
kolejce qptr – indeks początku
kolejki
Wyjście:
Wartość elementu na początku
kolejki lub wartość specjalna, jeśli
kolejka jest pusta.
Lista kroków:
K01 Jeśli qcnt = 0, to zakończ z
wynikiem wartość specjalna ;
kolejka pusta?
K02: Zakończ z wynikiem Q[qptr]
C++
typ_danych front() { if(qcnt) return
Q[qptr]; else return
wartość_specjalna; }
Wejście
n – liczba elementów w tablicy Q – tablica, w której
przechowywana jest kolejka qcnt – liczba
C++
elementów przechowywana w kolejce
qptr – indeks początku kolejki
v – dopisywany element
Wyjście:
Jeśli w tablicy jest miejsce, to do kolejki zostaje
dopisana nowa wartość. Inaczej kolejka nie jest
zmieniana.
Elementy pomocnicze:
i – indeks
Lista kroków:
K01 Jeśli qcnt = n, to zakończ ; sprawdzamy, czy w
tablicy jest miejsce na nowy element K02: i ← qptr
+ qcnt ; wyznaczamy położenie końca kolejki
K03: Jeśli i ≥ n, to i ← i - n ; korygujemy i w razie
potrzeby
K04: Q[i] ← v ; umieszczamy element na końcu kolejki
K05: qcnt ← qcnt + 1 ; zwiększamy liczbę elementów
K06: Zakończ
void push(typ_danych v) { int i; if(qcnt
< n) { i = qptr + qcnt++; if(i >= n)
i -= n; Q[i] = v; } }
Wejście
n – liczba elementów w tablicy
Q – tablica, w której
przechowywana jest kolejka qcnt –
liczba elementów przechowywana w
kolejce
qptr – indeks początku kolejki Wyjście:
Kolejka pomniejszona o pierwszy
element.
Lista kroków:
K01 Jeśli qcnt = 0, to zakończ ;
sprawdzamy, czy kolejka zawiera
jakieś elementy
K02: qcnt ← qcnt - 1 ; zmniejszamy
licznik elementów
K03: qptr ← qptr + 1 ; przesuwamy
początek kolejki
K04: Jeśli qptr = n, to qptr ← 0 ;
korygujemy indeks początku kolejki
K05: Zakończ

C++

void pop() { if(qcnt) { qcnt--;
qptr++; if(qptr == n) qptr = 0; } }
Poniższy program przedstawia sposób implementacji kolejki w tablicy. Tworzy
on obiekt zawierający tablicę liczb całkowitych, wskaźnik początku kolejki i
licznik jej elementów oraz metody obsługi tej struktury. Rozmiar tablicy jest
określany przy tworzeniu obiektu. W kolejce zostaje zapisanych 10 liczb, a
następnie są one odczytywane i wyświetlane.

// Kolejka w tablicy

queue::queue(int x)


// Data: 28.10.2012

{

{
i = qptr + qcnt++;

// (C)2012 mgr Jerzy Wałaszek

n = x;

if(i >= n) i -= n;

//------------------------------

Q = new int[x];


qptr = qcnt = 0;

Q[i] = v;
}

#include <iostream>

}

}

using namespace std;

// Destruktor - zwalnia tablicę dynamiczną

// Usuwa z kolejki

//----------------------------------------

//----------------

queue::~queue()

void queue::pop(void)

{

{

const int MAXINT = -2147483647;

// Definicja typu obiektowego queue


//---------------------------------


class queue

{
qcnt--;
qptr++;

//---------------------------------


bool queue::empty(void)

{

}

//---------------

// Program główny

//---------------
int n;

int qptr; // wskaźnik początku kolejki


int qcnt; // licznik elementów


int * Q;

public:
{


private:

if(qcnt)

// Sprawdza, czy kolejka jest pusta

// tablica dynamiczna
}



// rozmiar tablicy
delete [] Q;
if(qptr == n) qptr = 0;
}
return !qcnt;
}

// Zwraca początek kolejki.

queue(int x); // konstruktor

// Wartość specjalna to -MAXINT

~queue();

//-----------------------------

int main()

bool empty(void);

int queue::front(void)

{

int front(void);

{

queue Q(10); // tworzymy kolejkę na 10 elementów

void push(int v);

if(qcnt) return Q[qptr];

int i;

void pop(void);

return -MAXINT;
// destruktor

};

}

for(i = 1; i <= 10; i++) Q.push(i);

//---------------------

// Zapisuje do kolejki

while(!Q.empty())

// Metody obiektu queue

//--------------------

{

//---------------------

void queue::push(int v)


{


// Konstruktor - tworzy tablicę dla kolejki

int i;


//-----------------------------------------

if(qcnt < n)

cout << Q.front() << endl;
Q.pop();
}
}
Do realizacji kolejki posłużymy się lekko zmodyfikowaną listą jednokierunkową.
Będziemy potrzebowali dwóch wskaźników:
head – wskaźnik pierwszego elementu na liście tail – wskaźnik ostatniego
elementu na liście
Nowe elementy dodajemy na koniec listy – dzięki wskaźnikowi tail szybko
będziemy mogli znaleźć ostatni element i dołączyć za nim element
wstawiany. Pobieranie elementów będzie się odbywało z początku listy.
Budowa elementów listy jest typowa: każdy zawiera pole next, które
wskazuje kolejny element na liście, oraz pole data, które zawiera
przechowywane dane:
Wejście
head – wskaźnik początku kolejki
Wyjście:
True, jeśli kolejka jest pusta, inaczej
false
Lista kroków:
K01 Jeśli head = nil, to zakończ z
wynikiem true
K02: Zakończ z wynikiem false
C++
bool empty(void) { return !head; }
Wejście
head – wskaźnik pierwszego
elementu listy
Wyjście:
Wartość elementu na początku
kolejki lub wartość specjalna, jeśli
kolejka jest pusta.
Lista kroków:
K01 Jeśli head = 0, to zakończ z
wynikiem wartość specjalna ;
kolejka pusta?
K02: Zakończ z wynikiem
(head→data)
C++
typ_danych front() { if(head) return
head->data; else return
wartość_specjalna; }
Wejście
head – wskaźnik pierwszego elementu listy tail –
wskaźnik ostatniego elementu listy
v – dopisywany element
Wyjście:
Kolejka z dopisanym na końcu elementem o wartości
v.
Elementy pomocnicze:
p – wskaźnik elementu listy
Lista kroków:
K01 Utwórz nowy element listy
K02: p ← adres nowego elementu
K03: (p→next) ← nil ; inicjujemy pola nowego
elementu
K04: (p→data) ← v
K05: Jeśli tail ≠ nil, to idź do K08 ;
sprawdzamy, czy lista jest pusta
K06: head ← p ; jeśli tak, to wprowadzamy do niej
element jako pierwszy i ostatni
K07 Idź do K09
K08: (tail→next) ← p ; inaczej element dołączamy na
koniec listy
K09: tail ← p ; ustawiamy element jako ostatni na
liście
K10: Zakończ


C++
void push(typ_danych v) { slistEl *
p = new slistEl; p->next = NULL;
p->data = v; if(tail) tail->next =
p; else head = p; tail = p; }
Wejście
head – wskaźnik pierwszego elementu listy
tail – wskaźnik ostatniego elementu listy

Wyjście:
Kolejka pomniejszona o pierwszy element.
Elementy pomocnicze:
p – wskaźnik elementu listy
Lista kroków:
K01 Jeśli head = nil, to zakończ ; jeśli lista
jest pusta, kończymy
K02: p ← head ;zapamiętujemy adres
pierwszego elementu
K03: head ← (head→ne
xt) ;odłączamy od listy pierwszy element K04:
Jeśli head = nil, to tail ← nil ; jeśli lista
stała się pusta, t
o nie posiada ostatniego elementu K05: Usuń
z pamięci element wskazywany przez p
K06: Zakończ

C++
void pop() { if(head) { slistEl * p =
head; head = head->next;
if(!head) tail = NULL; delete p; } }
Poniższy program przedstawia sposób implementacji kolejki za pomocą listy. W
kolejce zostaje zapisanych 10 liczb, a następnie są one odczytywane i
wyświetlane.

// Kolejka na liście

// Data: 28.10.2012

p->data = v;


// (C)2012 mgr Jerzy Wałaszek

if(tail) tail->next = p;
// Konstruktor - tworzy pustą listę


//------------------------------
else

//---------------------------------

tail = p;

#include <iostream>

queue::queue()

}

{

using namespace std;

// Usuwa z kolejki

//----------------

void queue::pop(void)
{

//---------------------
head = tail = NULL;


const int MAXINT = -2147483647;

}
head = p;

// Destruktor - usuwa listę z pamięci


//-----------------------------------

if(head)
{

// Definicja typu elementów listy

queue::~queue()


//-------------------------------

{

slistEl * p = head;

struct slistEl


head = head->next;

{


if(!head) tail = NULL;

slistEl * next;

int data;

while(head) pop();
}
delete p;

};

// Sprawdza, czy kolejka jest pusta

}

//---------------------------------

}

bool queue::empty(void)
{

//---------------

// Program główny

// Definicja typu obiektowego queue


//---------------------------------


class queue

}

//---------------

{

// Zwraca początek kolejki.

int main()

slistEl * head;

// Wartość specjalna to -MAXINT

{

slistEl * tail;

//-----------------------------

queue Q; // kolejka

int queue::front(void)

int i;

{

for(i = 1; i <= 10; i++) Q.push(i);

while(!Q.empty())

{


private:
return !head;
public:

queue();
// konstruktor


~queue();
// destruktor


bool empty(void);

int front(void);

void push(int v);

// Zapisuje do kolejki


void pop(void);

//--------------------


void queue::push(int v)


{


if(head) return head->data;
else
return -MAXINT;
}

};

//---------------------

slistEl * p = new slistEl;

// Metody obiektu queue

p->next = NULL;
cout << Q.front() << endl;
Q.pop();
}
}
Jeśli do implementacji kolejki zastosujemy listę cykliczną, to otrzymamy tzw.
kolejkę cykliczną. W porównaniu ze zwykłą kolejką, kolejka cykliczna
wymaga tylko jednego wskaźnika tail, który wskazuje koniec kolejki.
Początek kolejki jest następnikiem jej ostatniego elementu.
Wejście
tail – wskaźnik końca kolejki
Wyjście:
True, jeśli kolejka jest pusta, inaczej
false
Lista kroków:
K01 Jeśli tail = nil, to zakończ z
wynikiem true
K02: Zakończ z wynikiem false
C++
bool empty(void) { return !tail; }
Wejście
tail – wskaźnik ostatniego elementu
listy
Wyjście:
Wartość elementu na początku
kolejki lub wartość specjalna, jeśli
kolejka jest pusta.
Lista kroków:
K01 Jeśli tail = 0, to zakończ z
wynikiem wartość specjalna ;
kolejka pusta?
K02: Zakończ z wynikiem
((tail→next)→data) ; zwróć wartość
następnego elementu za ostatnim
C++
typ_danych front() { if(tail) return tail>next->data; else return
wartość_specjalna; }
Wejście
tail – wskaźnik ostatniego elementu listy v –
dopisywany element
C++
Wyjście:
Kolejka z dopisanym na końcu elementem o wartości
v.
Elementy pomocnicze:
p – wskaźnik elementu listy
Lista kroków:
K01 Utwórz nowy element listy
K02: p ← adres nowego elementu K
03: (p→data) ← v ; inicjujemy pola nowego elementu
K04: Jeśli tail = nil, to idź do K08 ; sprawdzamy, czy
kolejka jest pusta
K05: (p→next) ← (tail→next) ; następnikiem jest
pierwszy element
K06: (tail→next) ← p ; element wstawiamy na listę za
ostatnim
K07 Idź do K09
K08: (p→next) ← p ; następnikiem jest ten sam
element
K09: tail ← p ; nowy element staje się ostatnim
K10: Zakończ
void push(typ_danych v) { slistEl * p =
new slistEl; p->data = v; if(tail) {
p->next = tail->next; tail->next
= p; } else p->next = p; tail = p; }
Wejście
tail – wskaźnik ostatniego elementu listy
Wyjście:

C++
Kolejka pomniejszona o pierwszy element.
Elementy pomocnicze:
p – wskaźnik elementu listy
Lista kroków:
K01 Jeśli tail = nil, to zakończ ; jeśli lista jest
pusta, kończymy
K02: p ← (tail→next) ; w p pierwszy element
K03: Jeśli (p→next) = p, to idź do K06 ;
kolejka zawiera tylko jeden element?
K04: (tail→next) ← (p→next) ; usuwamy
pierwszy element z listy
K05: Idź do K07
K06: tail ← nil ; tworzymy pustą listę
K07: Usuń z pamięci element wskazywany
przez p
K08: Zakończ
void pop() { if(tail) { slistEl * p = tail>next; if(p->next != p) tail->next
= p->next; else tail = NULL;
delete p; } }
Poniższy program przedstawia sposób implementacji kolejki cyklicznej. W
kolejce zostaje zapisanych 10 liczb, a następnie są one odczytywane i
wyświetlane.

// Kolejka cykliczna

// Konstruktor - tworzy pustą listę

p->next

// Data: 29.10.2012

//---------------------------------

tail->next = p;

// (C)2012 mgr Jerzy Wałaszek

queue::queue()

}

//------------------------------

{

else p->next = p;
tail = NULL;

= tail->next;
tail = p;


#include <iostream>

}

}

using namespace std;

// Destruktor - usuwa listę z pamięci

// Usuwa z kolejki

//-----------------------------------

//----------------

queue::~queue()

void queue::pop(void)

{

{

const int MAXINT = -2147483647;

// Definicja typu elementów listy


//-------------------------------


struct slistEl

{
while(tail) pop();
}

if(tail)

{

slistEl * p = tail->next;

// Sprawdza, czy kolejka jest pusta

if(p->next != p) tail->next = p->next;

slistEl * next;

//---------------------------------

else

int data;

bool queue::empty(void)

delete p;

{


};
return !tail;


// Definicja typu obiektowego queue

//---------------------------------



}

}

}

//---------------
class queue

// Zwraca początek kolejki.

// Program główny
{

// Wartość specjalna to -MAXINT

//---------------

//-----------------------------

int queue::front(void)

int main()

{

{
private:
slistEl * tail;


tail = NULL;
public:

queue();
// konstruktor

if(tail) return tail->next->data;

queue Q; // kolejka

~queue();
// destruktor

else

int i;

bool empty(void);

int front(void);

for(i = 1; i <= 10; i++) Q.push(i);



return -MAXINT;

}
void push(int v);

// Zapisuje do kolejki
void pop(void);

//--------------------

while(!Q.empty())

void queue::push(int v)

{

{

};

//---------------------

slistEl * p = new slistEl;


// Metody obiektu queue

p->data = v;


//---------------------

if(tail)


{
cout << Q.front() << endl;
Q.pop();
}
}

Podobne dokumenty