Marcin Matusiak i Łukasz Stasiak

Podobne dokumenty
Wykład 5 Wybrane zagadnienia programowania w C++ (c.d.)

Programowanie i struktury danych

Algorytmy i. Wykład 3: Stosy, kolejki i listy. Dr inż. Paweł Kasprowski. FIFO First In First Out (kolejka) LIFO Last In First Out (stos)

Lista, Stos, Kolejka, Tablica Asocjacyjna

Dynamiczny przydział pamięci w języku C. Dynamiczne struktury danych. dr inż. Jarosław Forenc. Metoda 1 (wektor N M-elementowy)

Dynamiczne struktury danych

INFORMATYKA W SZKOLE. Podyplomowe Studia Pedagogiczne. Dr inż. Grażyna KRUPIŃSKA. D-10 pokój 227

Listy powiązane zorientowane obiektowo

KOLEJKA (QUEUE) (lista fifo first in, first out)

ZASADY PROGRAMOWANIA KOMPUTERÓW

Dynamiczne struktury danych

Podstawy programowania skrót z wykładów:

Podstawowe struktury danych

Struktura danych. Sposób uporządkowania informacji w komputerze. Na strukturach danych operują algorytmy. Przykładowe struktury danych:

Programowanie i struktury danych

Programowanie i struktury danych. Wykład 4 Dr Piotr Cybula

Programowanie obiektowe i C++ dla matematyków

r. Tablice podstawowe operacje na tablicach

Materiał uzupełniający do ćwiczen z przedmiotu: Programowanie w C ++ - ćwiczenia na wskaźnikach

Wykład 4. Klasa List Kolejki Stosy Słowniki

Mechanizm dziedziczenia

Programowanie i struktury danych 1 / 44

Programowanie obiektowe - Przykładowe zadania egzaminacyjne (2005/2006)

Zmienne i struktury dynamiczne

Dariusz Brzeziński. Politechnika Poznańska, Instytut Informatyki

Wstęp do informatyki- wykład 12 Funkcje (przekazywanie parametrów przez wartość i zmienną)

Wstęp do wiadomości teoretycznych (nie, nie jest to masło maślane ani wstęp, wstępów proszę cierpliwie czytać)

DYNAMICZNE PRZYDZIELANIE PAMIECI

Temat: Dynamiczne przydzielanie i zwalnianie pamięci. Struktura listy operacje wstawiania, wyszukiwania oraz usuwania danych.

Algorytmy i Struktury Danych.

Wskaźnik może wskazywać na jakąś zmienną, strukturę, tablicę a nawet funkcję. Oto podstawowe operatory niezbędne do operowania wskaźnikami:

Zajęcia nr 2 Programowanie strukturalne. dr inż. Łukasz Graczykowski mgr inż. Leszek Kosarzewski Wydział Fizyki Politechniki Warszawskiej

Algorytmy i Struktury Danych.

8. Wektory. Przykłady Napisz program, który pobierze od użytkownika 10 liczb, a następnie wypisze je w kolejności odwrotnej niż podana.

2. Klasy cz. 2 - Konstruktor kopiujący. Pola tworzone statycznie i dynamicznie - Funkcje zaprzyjaźnione - Składowe statyczne

O podstawowych operacjach na tablicach. Mateusz Ziółkowski, MBiU II

E S - uniwersum struktury stosu

Wstęp do Programowania 2

Wskaźniki i dynamiczna alokacja pamięci. Spotkanie 4. Wskaźniki. Dynamiczna alokacja pamięci. Przykłady

KLASA UCZEN Uczen imię, nazwisko, średnia konstruktor konstruktor Ustaw Wyswietl Lepszy Promowany

W2 Wprowadzenie do klas C++ Klasa najważniejsze pojęcie C++. To jest mechanizm do tworzenia obiektów. Deklaracje klasy :

Struktury danych: stos, kolejka, lista, drzewo

Podstawy programowania obiektowego

Kiedy potrzebne. Struktura (rekord) Struktura w języku C# Tablice struktur. struktura, kolekcja

STL: Lekcja 1&2. Filozofia STL

Wykład 4: Klasy i Metody

Wskaźniki. nie są konieczne, ale dają językowi siłę i elastyczność są języki w których nie używa się wskaźników typ wskaźnikowy typ pochodny:

Programowanie komputerowe. Zajęcia 4

Algorytmy i złożoności. Wykład 3. Listy jednokierunkowe

Składnia C++ Programowanie Obiektowe Mateusz Cicheński

Informacje ogólne. Karol Trybulec p-programowanie.pl 1. 2 // cialo klasy. class osoba { string imie; string nazwisko; int wiek; int wzrost;

public: // interfejs private: // implementacja // składowe klasy protected: // póki nie będziemy dziedziczyć, // to pole nas nie interesuje

Paradygmaty programowania. Paradygmaty programowania

Deklaracja struktury w C++

Programowanie obiektowe

Wykład 8: klasy cz. 4

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 6

1 Wskaźniki. 1.1 Główne zastosowania wskaźników

KLASA UCZEN Uczen imię, nazwisko, średnia konstruktor konstruktor Ustaw Wyswietl Lepszy Promowany

wykład IV uzupełnienie notatek: dr Jerzy Białkowski Programowanie C/C++ Język C, a C++. wykład IV dr Jarosław Mederski Spis Język C++ - wstęp

Zajęcia nr 5 Algorytmy i wskaźniki. dr inż. Łukasz Graczykowski mgr inż. Leszek Kosarzewski Wydział Fizyki Politechniki Warszawskiej

Laboratorium z przedmiotu Programowanie obiektowe - zestaw 04

Techniki Programowania wskaźniki

Programowanie w C++ Wykład 5. Katarzyna Grzelak. 16 kwietnia K.Grzelak (Wykład 1) Programowanie w C++ 1 / 27

Opis zagadnieo 1-3. Iteracja, rekurencja i ich realizacja

Stos liczb całkowitych

Liczby całkowite i rzeczywiste

. Podstawy Programowania 2. Jednokierunkowa lista liniowa. Arkadiusz Chrobot. 28 marca 2017

Wstęp do programowania

Podstawy informatyki. Elektrotechnika I rok. Język C++ Operacje na danych - wskaźniki Instrukcja do ćwiczenia

Listy, kolejki, stosy

Programowanie w języku C++

Struktury danych (I): kolejka, stos itp.

Podczas dziedziczenia obiekt klasy pochodnej może być wskazywany przez wskaźnik typu klasy bazowej.

Obiektowy PHP. Czym jest obiekt? Definicja klasy. Składowe klasy pola i metody

Wstęp do programowania obiektowego. WYKŁAD 3 Dziedziczenie Pola i funkcje statyczne Funkcje zaprzyjaźnione, this

Programowanie Obiektowew języku C++ Zadania L4

Jzyk C++ cz 3. Jarosław Gramacki Instytut Informatyki i Elektroniki ( $)*)+' *, - ( ' )*'.' '',*/ *, ','*0) 1 / ) %*+ 2'' 2" ( $%%) )'20 )*0) 1 / )

Operatory na rzecz typu TString

Uniwersytet Zielonogórski Instytut Sterowania i Systemów Informatycznych. Ćwiczenie 3 stos Laboratorium Metod i Języków Programowania

Wyjątki (exceptions)

Podstawy Programowania 2 Jednokierunkowa lista liniowa. Plan. Jednokierunkowa lista liniowa. Jednokierunkowa lista liniowa. Notatki. Notatki.

PROE wykład 2 operacje na wskaźnikach. dr inż. Jacek Naruniec

Zasady programowania Dokumentacja

Programowanie obiektowe i C++ dla matematyków


Języki i techniki programowania Ćwiczenia 2

Mechanizm dziedziczenia

Struktury. Przykład W8_1

Projektowanie klas c.d. Projektowanie klas przykład

Algorytmika i programowanie. Wykład 2 inż. Barbara Fryc Wyższa Szkoła Informatyki i Zarządzania w Rzeszowie

Wstęp do programowania

Struktura danych. Sposób uporządkowania informacji w komputerze. Na strukturach danych operują algorytmy. Przykładowe struktury danych:

MACIERZE. Sobiesiak Łukasz Wilczyńska Małgorzata

Programowanie obiektowe

(3 kwiecień 2014) Marika Pankowska Kamila Pietrzak

TEMAT : KLASY DZIEDZICZENIE

Laboratorium 6: Dynamiczny przydział pamięci. dr inż. Arkadiusz Chrobot dr inż. Grzegorz Łukawski

Kurs programowania. Wykład 9. Wojciech Macyna. 28 kwiecień 2016

Wstęp do programowania

Transkrypt:

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 //--------------------- //-------------------------------- ------- 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; } 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(); } } // Konstruktor - tworzy tablicę dla stosu // Zapisuje na stos //-----------------

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 //------------------------------ #include <iostream> using namespace std; // Definicja typu obiektowego stack //--------------------------------- { struct slistel slistel * next; int data; }; //------------ { stack::stack() S = NULL; } // Destruktor - zwalnia tablicę dynamiczną //---------------------------------------- { stack::~stack() while(s) pop(); } // Sprawdza, czy stos jest pusty //------------------------------ e->data = v; e->next = S; S = e; } // Usuwa ze stosu //--------------- { if(s) { void stack::pop(void) slistel * e = S; S = S->next; delete e; } } { class stack private: slistel * S; // lista przechowująca stos public: stack(); // konstruktor ~stack(); // destruktor bool empty(void); slistel * top(void); void push(int v); void pop(void); }; //--------------------- // Metody obiektu stack //--------------------- // Konstruktor { bool stack::empty(void) return!s; } // Zwraca szczyt stosu //-------------------- { slistel * stack::top(void) return S; } // Zapisuje na stos //----------------- void stack::push(int v) { slistel * e = new slistel; //--------------- // Program główny //--------------- { int main() stack S; int i; for(i = 1; i <= 10; i++) S.push(i); while(!s.empty()) { cout << S.top()->data << endl; S.pop(); } }

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 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 C++ 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 // Data: 28.10.2012 // (C)2012 mgr Jerzy Wałaszek //------------------------------ #include <iostream> queue::queue(int x) { n = x; Q = new int[x]; qptr = qcnt = 0; } { i = qptr + qcnt++; if(i >= n) i -= n; Q[i] = v; } } using namespace std; const int MAXINT = -2147483647; // Definicja typu obiektowego queue //--------------------------------- { class queue private: int n; // rozmiar tablicy int qptr; // wskaźnik początku kolejki int qcnt; // licznik elementów int * Q; // tablica dynamiczna public: queue(int x); // konstruktor ~queue(); // destruktor bool empty(void); int front(void); void push(int v); void pop(void); }; // Destruktor - zwalnia tablicę dynamiczną //---------------------------------------- { queue::~queue() delete [] Q; } // Sprawdza, czy kolejka jest pusta //--------------------------------- { bool queue::empty(void) return!qcnt; } // Zwraca początek kolejki. // Wartość specjalna to -MAXINT //----------------------------- { int queue::front(void) if(qcnt) return Q[qptr]; return -MAXINT; } // Usuwa z kolejki //---------------- { void queue::pop(void) if(qcnt) { qcnt--; qptr++; if(qptr == n) qptr = 0; } } //--------------- // Program główny //--------------- { int main() queue Q(10); // tworzymy kolejkę na 10 elementów int i; for(i = 1; i <= 10; i++) Q.push(i); //--------------------- // Metody obiektu queue //--------------------- // Konstruktor - tworzy tablicę dla kolejki //----------------------------------------- // Zapisuje do kolejki //-------------------- void queue::push(int v) { int i; if(qcnt < n) while(!q.empty()) { 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 // (C)2012 mgr Jerzy Wałaszek //------------------------------ #include <iostream> using namespace std; const int MAXINT = -2147483647; // Definicja typu elementów listy //------------------------------- { struct slistel slistel * next; int data; }; // Definicja typu obiektowego queue //--------------------------------- { class queue private: slistel * head; slistel * tail; public: queue(); // konstruktor ~queue(); // destruktor bool empty(void); int front(void); void push(int v); void pop(void); }; //--------------------- // Metody obiektu queue //--------------------- // Konstruktor - tworzy pustą listę //--------------------------------- { queue::queue() head = tail = NULL; } // Destruktor - usuwa listę z pamięci //----------------------------------- { queue::~queue() while(head) pop(); } // Sprawdza, czy kolejka jest pusta //--------------------------------- { bool queue::empty(void) return!head; } // Zwraca początek kolejki. // Wartość specjalna to -MAXINT //----------------------------- { int queue::front(void) if(head) return head->data; else return -MAXINT; } // Zapisuje do kolejki //-------------------- void queue::push(int v) { slistel * p = new slistel; p->next = NULL; p->data = v; if(tail) tail->next = p; else head = p; tail = p; } // Usuwa z kolejki //---------------- { void queue::pop(void) if(head) { slistel * p = head; head = head->next; if(!head) tail = NULL; delete p; } } //--------------- // Program główny //--------------- { int main() queue Q; // kolejka int i; for(i = 1; i <= 10; i++) Q.push(i); while(!q.empty()) { 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 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 C++ 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: 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 C++ 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 // Data: 29.10.2012 // (C)2012 mgr Jerzy Wałaszek //------------------------------ #include <iostream> // Konstruktor - tworzy pustą listę //--------------------------------- { queue::queue() tail = NULL; } p->next = tail->next; tail->next = p; } else p->next = p; tail = p; } using namespace std; const int MAXINT = -2147483647; // Definicja typu elementów listy //------------------------------- { struct slistel slistel * next; int data; }; // Definicja typu obiektowego queue //--------------------------------- { class queue private: slistel * tail; public: queue(); // konstruktor ~queue(); // destruktor bool empty(void); int front(void); void push(int v); void pop(void); }; //--------------------- // Metody obiektu queue //--------------------- // Destruktor - usuwa listę z pamięci //----------------------------------- { queue::~queue() while(tail) pop(); } // Sprawdza, czy kolejka jest pusta //--------------------------------- { bool queue::empty(void) return!tail; } // Zwraca początek kolejki. // Wartość specjalna to -MAXINT //----------------------------- { int queue::front(void) if(tail) return tail->next->data; else return -MAXINT; } // Zapisuje do kolejki //-------------------- void queue::push(int v) { slistel * p = new slistel; p->data = v; if(tail) { // Usuwa z kolejki //---------------- { void queue::pop(void) if(tail) { slistel * p = tail->next; if(p->next!= p) tail->next = p->next; else tail = NULL; delete p; } } //--------------- // Program główny //--------------- { int main() queue Q; // kolejka int i; for(i = 1; i <= 10; i++) Q.push(i); while(!q.empty()) { cout << Q.front() << endl; Q.pop(); } }