Podstawy Programowania 2 Kolejki i ich zastosowania. Plan. Kolejki i ich klasyfikacja. Kolejki i ich klasyfikacja Kolejki dwustronne. Notatki.

Podobne dokumenty
. Podstawy Programowania 2. Kolejki i ich zastosowania. Arkadiusz Chrobot. 21 marca 2016

Algorytm DFS Wprowadzenie teoretyczne. Algorytm DFS Wprowadzenie teoretyczne. Algorytm DFS Animacja. Algorytm DFS Animacja. Notatki. Notatki.

. Podstawy Programowania 2. Algorytmy dfs i bfs. Arkadiusz Chrobot. 2 czerwca 2019

. Podstawy Programowania 2. Dwukierunkowa lista liniowa. Arkadiusz Chrobot. 7 kwietnia 2019

Podstawy Programowania 2 Dwukierunkowa lista liniowa. Plan. Wstęp. Implementacja. Notatki. Notatki. Notatki. Notatki.

. Podstawy Programowania 2. Dwukierunkowa lista cykliczna. Arkadiusz Chrobot. 24 kwietnia 2016

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

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

. Podstawy Programowania 2. Algorytmy z nawrotami. Arkadiusz Chrobot. 9 czerwca 2019

Podstawy Programowania 2 Algorytmy z nawrotami. Plan. Wstęp

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

Podstawy Programowania 2 Jednokierunkowa lista liniowa i rekurencja. Plan. Wstęp. Założenia. Notatki. Notatki. Notatki. Notatki.

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

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

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

Wyszukiwanie w BST Minimalny i maksymalny klucz. Wyszukiwanie w BST Minimalny klucz. Wyszukiwanie w BST - minimalny klucz Wersja rekurencyjna

Podstawy Programowania 2 Grafy i ich reprezentacje. Plan. Wstęp. Teoria grafów Graf skierowany. Notatki. Notatki. Notatki. Notatki.

ZASADY PROGRAMOWANIA KOMPUTERÓW

. Podstawy Programowania 2. Drzewa bst - część druga. Arkadiusz Chrobot. 12 maja 2019

. Podstawy Programowania 2. Grafy i ich reprezentacje. Arkadiusz Chrobot. 9 czerwca 2016

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

Laboratorium z przedmiotu Programowanie obiektowe - zestaw 04

Podstawy programowania, Poniedziałek , 8-10 Projekt, część 1

Programowanie obiektowe

Lista 5 Typy dynamiczne kolejka

. Podstawy Programowania 2. Drzewa bst - część pierwsza. Arkadiusz Chrobot. 22 maja 2016

METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

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

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

Wskaźniki w C. Anna Gogolińska

Dynamiczne struktury danych

Tablice (jedno i wielowymiarowe), łańcuchy znaków

Struktury. Przykład W8_1

Jeśli chcesz łatwo i szybko opanować podstawy C++, sięgnij po tę książkę.

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

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

Podstawy programowania. Wykład 6 Wskaźniki. Krzysztof Banaś Podstawy programowania 1

Niezwykłe tablice Poznane typy danych pozwalają przechowywać pojedyncze liczby. Dzięki tablicom zgromadzimy wiele wartości w jednym miejscu.


Podstawy programowania. Wykład 7 Tablice wielowymiarowe, SOA, AOS, itp. Krzysztof Banaś Podstawy programowania 1

1 Podstawy c++ w pigułce.

. Podstawy Programowania 2. Stos i jego zastosowania. Arkadiusz Chrobot. 6 marca 2018

Podstawy programowania komputerów

Podstawy Programowania 2 Stos i jego zastosowania. Plan. Abstrakcyjne struktury danych. Stos - wprowadzenie. Notatki. Notatki. Notatki.

Lab 9 Podstawy Programowania

Wstęp do programowania

Wykład 8: klasy cz. 4

Widoczność zmiennych Czy wartości każdej zmiennej można zmieniać w dowolnym miejscu kodu? Czy można zadeklarować dwie zmienne o takich samych nazwach?

Techniki programowania INP001002Wl rok akademicki 2018/19 semestr letni. Wykład 3. Karol Tarnowski A-1 p.

E S - uniwersum struktury stosu

Deklaracja struktury w C++

Strona główna. Strona tytułowa. Programowanie. Spis treści. Sobera Jolanta Strona 1 z 26. Powrót. Full Screen. Zamknij.

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

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

Podstawy Programowania C++

Informatyka I. Klasy i obiekty. Podstawy programowania obiektowego. dr inż. Andrzej Czerepicki. Politechnika Warszawska Wydział Transportu 2018

Wykład 4: Klasy i Metody

lekcja 8a Gry komputerowe MasterMind

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

Wskaźniki. Przemysław Gawroński D-10, p marca Wykład 2. (Wykład 2) Wskaźniki 8 marca / 17

Listy, kolejki, stosy

INFORMATYKA Z MERMIDONEM. Programowanie. Moduł 5 / Notatki

Wstęp do programowania INP001213Wcl rok akademicki 2018/19 semestr zimowy. Wykład 8. Karol Tarnowski A-1 p.

Wskaźniki. Informatyka

Ćwiczenie 3 z Podstaw programowania. Język C++, programy pisane w nieobiektowym stylu programowania. Zofia Kruczkiewicz

Podstawy Programowania 2 Wskaźniki i zmienne dynamiczne. Plan. Wskaźniki - krótka powtórka. Wskaźniki - krótka powtórka Wartości wskaźników.

Zmienne, stałe i operatory

Pętle. Dodał Administrator niedziela, 14 marzec :27

Podstawy programowania komputerów

Zmienne i struktury dynamiczne

Wykład 3 Składnia języka C# (cz. 2)

Programowanie i struktury danych

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

. Podstawy Programowania 1. Funkcje. Arkadiusz Chrobot. 29 października 2015

TEMAT : KLASY DZIEDZICZENIE

Języki i techniki programowania Ćwiczenia 2

Lekcja : Tablice + pętle

Liczby losowe i pętla while w języku Python

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

Rozpoznawanie obrazu. Teraz opiszemy jak działa robot.

Wstęp do programowania INP001213Wcl rok akademicki 2017/18 semestr zimowy. Wykład 8. Karol Tarnowski A-1 p.

Podstawy programowania, Poniedziałek , 8-10 Projekt, część 3

Przychodnia 0. Stwórz projekt aplikacja konsolowa lub WPF (przemyśl wybór, bo zmiana może być czasochłonna). 1. Stwórz abstrakcyjną klasę Osoba.

Programowanie w języku Python. Grażyna Koba

Część 4 życie programu

Obiekt klasy jest definiowany poprzez jej składniki. Składnikami są różne zmienne oraz funkcje. Składniki opisują rzeczywisty stan obiektu.

. Podstawy Programowania 2. Wskaźniki i zmienne dynamiczne. Arkadiusz Chrobot. 25 lutego 2019

REKURENCJA W JĘZYKU HASKELL. Autor: Walczak Michał

Wskaźniki. Programowanie Proceduralne 1

Wykład 6_1 Abstrakcyjne typy danych stos Realizacja tablicowa i za pomocą rekurencyjnych typów danych

1 Podstawy c++ w pigułce.

4. Procesy pojęcia podstawowe

Zadanie nr 3: Sprawdzanie testu z arytmetyki

DYNAMICZNE PRZYDZIELANIE PAMIECI

Algorytmy i język C++

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

Zapis algorytmów: schematy blokowe i pseudokod 1

> C++ dynamiczna alokacja/rezerwacja/przydział pamięci. Dane: Iwona Polak. Uniwersytet Śląski Instytut Informatyki

Transkrypt:

Podstawy Programowania 2 Kolejki i ich zastosowania Arkadiusz Chrobot Zakład Informatyki 18 marca 2019 1 / 60 Plan Kolejki i ich klasyfikacja Testowanie niektórych operacji dynamicznych struktur danych Podsumowanie 2 / 60 Kolejki i ich klasyfikacja Kolejki, podobnie jak stos są abstrakcyjną strukturą danych, składającą się z połączonych ze sobą elementów przechowujących dane Inne są jednak reguły rządzące zarządzaniem tymi elementami Wyraz kolejka jest najczęściej rozumiany jako skrócona nazwa dla kolejka fifo i tak też będzie traktowany na tym wykładzie Skrót fifo oznacza w języku angielskim First In First Out, czyli w przekładzie polskim: Pierwszy Nadszedł, Pierwszy Wychodzi Konsekwencją tej reguły jest to, że elementy są dodawane do kolejki na tylko jednym z jej końców, a usuwane wyłącznie na drugim Koniec służący do usuwania elementów nazywany jest czołem lub głową kolejki (ang head), natomiast przeciwległy koniec, na którym elementy są dodawane, nazywa się ogonem (ang tail) kolejki 3 / 60 Kolejki i ich klasyfikacja Kolejki dwustronne Oprócz kolejek fifo istnieją także kolejki dwustronne, w których operacje dodawania i usuwania elementów mogą dotyczyć obu końców Wśród nich wyróżniamy dwa dodatkowe podtypy: kolejki dwustronne o ograniczonym wejściu - elementy mogą być usuwane na obu końcach kolejki, ale dodawane tylko na jednym, kolejki dwustronne o ograniczonym wyjściu - elementy mogą być dodawane na obu końcach kolejki, ale usuwane tylko na jednym 4 / 60

Dalsza część wykładu będzie dotyczyła wyłącznie kolejek fifo Takie kolejki mogą być zrealizowane w oparciu o dynamiczne struktury danych lub o tablicę Oba sposoby implementacji zostaną w ramach tego wykładu przedstawione W ostatniej części wykładu zostanie zaprezentowany prosty sposób testowania funkcji realizujących niektóre operacje na kolejkach i podobnych dynamicznych strukturach danych Sposób tworzenia i obsługi kolejek będzie przedstawiony na przykładzie kolejki przechowującej liczby typu int Rozpoczniemy od jej implementacji w postaci dynamicznej struktury danych 5 / 60 Tak, jak w przypadku stosu i innych abstrakcyjnych struktur danych do implementacji kolejki będziemy potrzebować typu bazowego oraz funkcji realizujących podstawowe operacje na tej strukturze W tym ostatnim przypadku niezbędnym minimum są dwie operacje: dodawanie elementu do kolejki i usuwanie elementu z kolejki Noszą one angielskie nazwy: enquene i dequeue Aby ułatwić ich wykonywanie do obsługi kolejek stosuje się dwa wskaźniki Jeden wskazuje na element początkowy i nazywa się zwyczajowo head, a drugi na element końcowy i nazywa się tail Pierwszy używany jest podczas usuwania elementu z kolejki, a drugi podczas dodawania W literaturze można spotkać również nazwy front i rear dla tych wskaźników 6 / 60 Program ilustrujący działanie kolejki fifo korzysta z funkcji zarządzających pamięcią na stercie oraz obsługujących standardowe wyjście (monitor) W związku z tym, w jego kodzie źródłowym zawarte są instrukcje włączające pliki nagłówkowe stdioh i stdlibh 7 / 60 Pliki nagłówkowe 1 #include<stdioh> 2 #include<stdlibh> 8 / 60

Typ bazowy kolejki fifo Zdefiniowany w programie typ bazowy kolejki fifo bazuje na strukturze i jest taki sam, z dokładnością do nazwy, jak typ bazowy stosu Może on również być w dowolny sposób modyfikowany, zawsze jednak musi zawierać pole wskaźnikowe pozwalające wiązać ze sobą elementy kolejki Definicja tego typu podana jest na następnym slajdzie 9 / 60 Typ bazowy kolejki fifo 1 struct fifo_node 3 int data; 4 struct fifo_node *next; 5 }; 10 / 60 Wskaźniki head i tail Jak wspomniano wcześniej, aby implementacja kolejki była efektywna niezbędne są dwa wskaźniki Jeden z nich będzie wskazywał na element początkowy kolejki, a drugi na końcowy Można te wskaźniki zadeklarować jako osobne zmienne lokalne lub globalne W opisywanym programie posłużymy się jednak osobną strukturą, w której te wskaźniki będą zaimplementowane jako pola Definicja typu tej struktury oraz deklaracja zmiennej globalnej tego typu zostały przedstawione na kolejnym slajdzie Ponieważ zmienne globalne mają zerową wartość początkową, to kolejka jest wstępnie kolejką pustą 11 / 60 Struktura wskaźników 1 struct fifo_pointers 3 struct fifo_node *head, *tail; 4 } fifo; 12 / 60

Operacja enqueue Mając zdefiniowaną strukturę wskaźników oraz typ bazowy kolejki fifo możemy przystąpić do zdefiniowania funkcji realizujących operacje na tej kolejce Zaczniemy od operacji dodania nowego elementu do kolejki Zakładamy, że będzie ona spełniała następujące warunki: Jeśli kolejka istnieje, to operacja dodaje nowy element na jej końcu, a jeśli kolejka nie istnieje (jest pusta), to operacja tworzy ją przydzielając pamięć na jej pierwszy element, inicjując go i dodając Jeśli nie uda się stworzyć nowego elementu, to stan kolejki pozostaje bez zmian W wyniku pomyślnego zakończenia operacji kolejka powiększa się o nowy element, a jeśli nie istniała to zostaje utworzona Na kolejnym slajdzie znajduje się kod źródłowy funkcji implementującej tę operację 13 / 60 Funkcja enqueue() 1 void enqueue(struct fifo_pointers *fifo, int data) 3 struct fifo_node *new_node = 4 (struct fifo_node *)malloc(sizeof(struct fifo_node)); 5 if(new_node) { 6 new_node->data = data; 7 new_node->next = NULL; 8 if(fifo->head==null) 9 fifo->head = fifo->tail = new_node; 10 else { 11 fifo->tail->next=new_node; 12 fifo->tail=new_node; 13 } 14 } else 15 fprintf(stderr,"nowy element nie został utworzony!\n"); 16 } 14 / 60 Funkcja enqueue() Opisywana operacja została w programie zaimplementowana w postaci funkcji o takiej samej nazwie Funkcja ta nie zwraca żadnej wartości W przypadku niepowodzenia utworzenia i dodania nowego elementu do kolejki wypisuje ona jedynie komunikat na ekranie Stan kolejki pozostaje w takim przypadku bez zmian Jeśli pozostanie ona pusta, to nie wpłynie to negatywnie na działanie pozostałych funkcji ją obsługujących, bo wszystkie one, zanim wykonają jakąkolwiek operację na przekazanej im kolejce, najpierw sprawdzają, czy nie jest ona pusta Do enqueue() przekazywana jest przez wskaźnik struktura zawierająca wskaźniki head i tail Ich wartości mogą być modyfikowane w funkcji i wtedy muszą być zachowane po jej zakończeniu, stąd taki sposób przekazania Drugi parametr funkcji służy do przekazania danej, która ma być zapisana w kolejce 15 / 60 Funkcja enqueue() W wierszach nr 3 i 4 funkcji przydzielana jest pamięć na nowy element kolejki Po sprawdzeniu w wierszu nr 5 czy ten przydział się powiódł wykonywana jest inicjacja pól nowo utworzonego elementu Pole next zyskuje wartość null, celem zaznaczenia, że będzie to ostatni element w kolejce Tworząc fragment funkcji enqueue() odpowiedzialny za dodanie nowego elementu na koniec kolejki należy rozpatrzeć dwa przypadki: 1 element dodawany jest na koniec istniejącej kolejki 2 element dodawany jest do pustej (nieistniejącej) kolejki Rozróżnienie między nimi zachodzi w 8 wierszu funkcji Jeśli oba wskaźniki na kolejkę mają wartość null, to znaczy, że występuje drugi przypadek i dlatego do wskaźników zapisywany jest adres nowego elementu, który staje się pierwszym i jednocześnie ostatnim elementem kolejki Wykonanie tego wariantu ilustrują rysunki na kolejnych slajdach 16 / 60

Funkcja enqueue() - tworzenie nowej kolejki head new_node tail null Kolejka przed wykonaniem 9 wiersza funkcji enqueue() 17 / 60 Funkcja enqueue() - tworzenie nowej kolejki head new_node tail null Kolejka po wykonaniu 9 wiersza funkcji enqueue() 17 / 60 Funkcja enqueue() Inaczej przebiega dodanie elementu do już istniejącej kolejki W wierszu nr 11 za pomocą wskaźnika tail funkcja enqueue() sięga do obecnego ostatniego elementu w kolejce i zapisuje w jego polu next adres nowego elementu W ten sposób staje się on ostatnim elementem kolejki Aby jednak pozostawić kolejkę w poprawnym stanie funkcja musi przed zakończeniem swojego działania zapewnić, że wskaźnik tail nadal będzie wskazywał na ostatni element kolejki Dlatego w wierszu nr 12 zapisywany jest w nim adres nowego elementu Proszę zwrócić uwagę, że wiersze 11 i 12 są wzajemnie ze sobą powiązane, co znaczy, że nie można zamienić ich kolejności Wiersz 12 można zastąpić wierszem: fifo->tail=fifo->tail->next; jednak ta instrukcja jest trochę mniej czytelna Będziemy jednak używać podobnych zapisów w następnych funkcjach i na następnych wykładach Komunikat w wierszu 15 wyświetlany jest tylko wtedy, gdy utworzenie nowego elementu się nie powiedzie Wówczas stan kolejki pozostaje bez zmian 18 / 60 Funkcja enqueue() Kolejne slajdy zawierają ilustrację dodania nowego elementu do jednoelementowej kolejki Działanie to jest takie samo dla kolejek zawierających więcej niż jeden element 19 / 60

Funkcja enqueue() - dodawanie nowego elementu head tail new_node null null Przed wykonaniem 11 wiersza funkcji enquene() 20 / 60 Funkcja enqueue() - dodawanie nowego elementu head tail new_node next null Po wykonaniu 11 wiersza funkcji enquene() 20 / 60 Funkcja enqueue() - dodawanie nowego elementu head tail new_node next null Po wykonaniu 12 wiersza funkcji enquene() 20 / 60 Operacja dequeue Operacja dequeue usuwa element z czoła (początku) kolejki fifo Operacja ta powinna spełniać następujące warunki: Jeśli kolejka nie istnieje, to stan jej wskaźników po wykonaniu operacji usuwania elementu nie może ulec zmianie - wartości obu powinny wynosić null Jeśli usuwany jest element z kolejki jednoelementowej, to po wykonaniu tej operacji oba wskaźniki kolejki powinny mieć wartość null Jeśli usuwany jest element z kolejki składającej się z więcej niż jednego elementu, to kolejka zostaje zmniejszona o jeden element, a po wykonaniu tej operacji wskaźniki poprawnie wskazują początek i koniec kolejki Implementację tej operacji w postaci funkcji przedstawiono na następnym slajdzie 21 / 60

Funkcja dequeue() 1 int dequeue(struct fifo_pointers *fifo) 3 if(fifo->head) { 4 struct fifo_node *tmp = fifo->head->next; 5 int data = fifo->head->data; 6 free(fifo->head); 7 fifo->head=tmp; 8 if(tmp==null) 9 fifo->tail = NULL; 10 return data; 11 } 12 return -1; 13 } 22 / 60 Funkcja dequeue() Proszę zwrócić uwagę, że funkcja dequeue() jest bardzo podobna do funkcji pop() implementowanej w przypadku stosu Podobnie jako ona zwraca liczbę -1 jeśli zostanie wywołania dla pustej kolejki Operacja usunięcia elementu z czoła kolejki jest bardzo podobna do operacji usunięcia elementu ze stosu Różnica występuje tylko w dwóch miejscach Po pierwsze wskaźniki head i tail są polami struktury, po drugie, zgodnie z podanymi na poprzednim slajdzie warunkami, konieczne jest nadanie wskaźnikowi tail wartości null po usunięciu elementu z kolejki jednoelementowej Instrukcje w wierszach 8 i 9 gwarantują spełnienie tego wymogu 23 / 60 Funkcje enquene() i dequeue() - podsumowanie Operacje enquene i dequeue są podstawowymi operacjami jakie należy zaimplementować dla kolejki fifo Stanowią one niezbędne minimum, aby móc posłużyć się taką strukturą danych w programie Na poprzednich slajdach zaprezentowano przykładowe ich implementacje Można je zrealizować także w inny sposób, np funkcja dequeue() mogłaby nic nie zwracać lub zwracać jedynie wartość sygnalizującą stan wykonania operacji usuwania elementu Do doczytania wartości pierwszego elementu kolejki mogłaby być zdefiniowana osobna funkcja Sposób implementacji tych funkcji zależy od potrzeb programisty i rozwiązywanego przez niego problemu 24 / 60 Wypisanie wartości elementów na ekran Implementacja operacji wypisania wartości elementów na ekran komputera nie jest obowiązkowa w implementacji kolejki fifo, ale jest dosyć wygodnym rozwiązaniem Na następnych slajdach zostaną przedstawione dwie funkcje, które realizują tę operację 25 / 60

Funkcja print_queue() 1 void print_queue(struct fifo_pointers fifo) 3 while(fifohead) { 4 printf("%d ",fifohead->data); 5 fifohead = fifohead->next; 6 } 7 puts(""); 8 } 26 / 60 Funkcja print_queue() Do tej funkcji struktura wskaźników kolejki jest przekazywana przez wartość To dlatego, że wygodniej będzie użyć w niej wskaźnika head do iterowania, czyli poruszania się po elementach kolejki, ale wiąże się to z modyfikacją jego wartości Te modyfikacje nie mogą jednak wyjść poza funkcję print_queue(), co zapewnia nam taki sposób przekazania Gdyby wskaźnik head po zakończeniu działania funkcji miał inną wartość niż przed jej rozpoczęciem, to zostałby zgubiony adres elementu początkowego kolejki W funkcji wykonywana jest pętla while tak długo, jak długo wskaźnik head będzie miał wartość różną od null, co oznacza, że w kolejce będą jeszcze elementy, których wartość nie została jeszcze wypisana na ekran Samo wypisanie wykonywane jest w wierszu nr 4 W piątym wierszu wskaźnik head jest przestawiany na następny element kolejki poprzez zapisanie w nim adresu z pola next wskazywanego przez niego elementu 27 / 60 Funkcja print_queue() - wersja z pętlą for 1 void print_queue_with_for(struct fifo_pointers fifo) 3 for(;fifohead;fifohead=fifohead->next) 4 printf("%d ",fifohead->data); 5 puts(""); 6 } 28 / 60 Funkcja print_queue() - wersja z pętlą for Ta sama operacja wypisania elementów kolejki fifo może być zrealizowana w języku C za pomocą pętli for, tak jak w funkcji zamieszczonej na poprzednim slajdzie Zmienną sterującą dla tej pętli jest wskaźnik head Proszę zwrócić uwagę, że pomijamy wyrażenie inicjujące Warunkiem kontynuacji jest to, że wskaźnik head będzie miał wartość różną od null, a wyrażenie zmieniające wartość tego wskaźnika przypisuje mu adres kolejnego elementu kolejki Zapis ten jest bardziej zwięzły niż w przypadku pętli while, ale trochę mniej czytelny 29 / 60

Przykład użycia - funkcja main() 1 int main(void) 3 int i; 4 for(i=0;i<20;i++) 5 enqueue(&fifo,i); 6 print_queue_with_for(fifo); 7 while(fifohead) 8 printf("%d ",dequeue(&fifo)); 9 puts(""); 10 return 0; 11 } 30 / 60 Przykład użycia - funkcja main() W funkcji main() programu wywoływane są wszystkie wcześniej zdefiniowane funkcje do obsługi kolejki fifo, za wyjątkiem print_queue() Można jej wywołaniem zastąpić wywołanie print_queue_with_for() lub po prostu dodać je do programu W wierszach 4 i 5 dodawane są do kolejki elementy zawierające liczby naturalne od 0 do 19, następnie wypisywana jest zawartość kolejki na ekran (wiersz 6), usuwane są z niej wszystkie elementy i ponownie wypisywane są na ekran ich wartości (wiesze 7 i 8) 31 / 60 Implementacja za pomocą tablicy może zostać zrealizowana za pomocą tablicy Będzie ona miała pojemność ograniczoną liczbą elementów tej tablicy, ale poza tym będzie zachowywała się jak jej odpowiedniczka zrealizowana za pomocą dynamicznej struktury danych Kolejkę całkowicie zapełnioną będziemy nazywać kolejką pełną Przykładowa implementacja takiej kolejki zostanie opisana na przykładzie programu, który tak jak ten wcześniej zaprezentowany, przechowuje w kolejce fifo liczby całkowite Wskaźniki head i tail zostaną w tej kolejce zastąpione zmiennymi indeksującymi o nazwach first i last Aby ułatwić realizację takiej kolejki można potraktować tablicę jako tablicę cykliczną, czyli taką, która nie ma początku ani końca Kolejkę uzyskaną za pomocą takiej tablicy przedstawia kolejny slajd 32 / 60 Implementacja za pomocą tablicy last 5 6 4 7 first 3 0 2 1 Częściowo wypełniona kolejka fifo 33 / 60

last Implementacja za pomocą tablicy Takie podejście ma dwie konsekwencje Z jednej strony wartości obu indeksów podczas operacji dodawania i usuwania elementów są jedynie zwiększane o jeden Z drugiej strony należy ustalić w jaki sposób będziemy wykrywać, że kolejka jest pusta lub pełna Można zliczać, za pomocą osobnej zmiennej, ile obecnie jest elementów w kolejce, ale inne rozwiązanie tego problemu podali Alfred V Aho, John E Hopcroft i Jeffrey D Ullman w książce Algorytmy i struktury danych Zaprezentowany program bazuje na ich rozwiązaniu Pełne i puste kolejki według ich propozycji są zilustrowane na kolejnych slajdach 34 / 60 Implementacja za pomocą tablicy 5 6 4 7 first 3 0 2 1 Pełna kolejka fifo 35 / 60 Implementacja za pomocą tablicy Pełna kolejka to taka, w której indeksy last i first różnią się wartościami o 2 modulo liczba elementów tablicy Proszę zwrócić uwagę, że przy takiej definicji warunku wypełnienia kolejki jeden element tablicy zawsze będzie pusty, tak jak pokazano to na rysunku 36 / 60 Implementacja za pomocą tablicy last first 5 6 4 7 3 0 2 1 Pusta kolejka fifo 37 / 60

Implementacja za pomocą tablicy Pusta kolejka to taka, w której indeksy last i first mają wartości różniące się o 1 modulo liczba elementów tablicy 38 / 60 Implementacja za pomocą tablicy - struktura kolejki 1 #include<stdioh> 2 #include<stdboolh> 3 4 #define FIFO_SIZE 20 5 6 struct queue 7 { 8 int elements[fifo_size], first, last; 9 } fifo; 39 / 60 Implementacja za pomocą tablicy - struktura kolejki Poprzedni slajd zawiera początek programu implementującego kolejkę w oparciu o tablicę Plik nagłówkowy stdlibh został zastąpiony plikiem stdboolh, ponieważ jedna z funkcji będzie zwracać wartość typ bool, a w programie nie będą wykorzystywane funkcje zarządzające pamięcią na stercie Stała fifo_size określa liczę elementów tablicy na której będzie oparta kolejka Pojemność tej kolejki będzie o jeden element mniejsza Tablica i indeksy kolejki zostały w programie zaimplementowane jako pola struktury Zmienna tego typu strukturalnego o nazwie fifo jest po prostu tą kolejką 40 / 60 Implementacja za pomocą tablicy - funkcja add_one() 1 int add_one(int index) 3 return (index+1)%fifo_size; 4 } 41 / 60

Implementacja za pomocą tablicy - funkcja add_one() Funkcja add_one() służy do zwiększania wartości indeksów kolejki o jeden Dzięki zastosowaniu operatora reszty z dzielenia w tej operacji, wartość żadnego z tych indeksów nie przekroczy dopuszczalnego zakresu Funkcja przez parametr przyjmuje bieżącą wartość indeksu, a zwraca następną 42 / 60 Implementacja za pomocą tablicy - funkcja make_empty() 1 void make_empty(struct queue *fifo) 3 fifo->first = 0; 4 fifo->last = FIFO_SIZE-1; 5 } 43 / 60 Implementacja za pomocą tablicy - funkcja make_empty() Funkcja make_empty() dokonuje inicjacji kolejki, czy nadania jej indeksom wartości neutralnych Indeks first będzie po jej wykonaniu określał pierwszy element tablicy, a indeks last ostatni 44 / 60 Implementacja za pomocą tablicy - funkcja is_empty() 1 bool is_empty(struct queue fifo) 3 return add_one(fifolast)==fifofirst; 4 } 45 / 60

Implementacja za pomocą tablicy - funkcja is_empty() Funkcja is_empty() zwraca wartość true jeśli nie ma elementów w kolejce lub false jeśli jest choć jeden Jej działanie polega na sprawdzeniu, czy zwiększona za pomocą wywołania funkcji add_one() wartość indeksu last odpowiada wartości indeksu first 1 Jeśli tak jest, to kolejka jest pusta 1 Proszę porównać z odpowiednią ilustracją na poprzednich slajdach 46 / 60 Implementacja za pomocą tablicy - funkcja first_one() 1 int first_one(struct queue fifo) 3 if(is_empty(fifo)==true) 4 return -1; 5 else 6 return fifoelements[fifofirst]; 7 } 47 / 60 Implementacja za pomocą tablicy - funkcja first_one() W opisywanym programie operacja dequeue będzie polegała jedynie na usunięciu pierwszego elementu kolejki Funkcja first_one() zwraca wartość pierwszego elementu kolejki Jest to ten, który określany jest przez indeks first Jeśli kolejka byłaby pusta, to funkcja zwraca wartość -1 48 / 60 Implementacja za pomocą tablicy - funkcja enqueue() 1 void enqueue(struct queue *fifo, int data) 3 4 if(add_one(add_one(fifo->last))!=fifo->first) 5 { 6 fifo->last = add_one(fifo->last); 7 fifo->elements[fifo->last] = data; 8 } else 9 fprintf(stderr, "Kolejka jest pełna!\n"); 10 } 49 / 60

Implementacja za pomocą tablicy - funkcja enqueue() Funkcja enqueue() dodaje do kolejki nowy element, zapisując w nim wartość przekazaną jej przez parametr data Zanim to jednak nastąpi funkcja ta najpierw upewnia się, że kolejka nie jest pełna Robi to dwukrotnie zwiększając za pomocą funkcji add_one() wartość indeksu last i porównując wynik z indeksem first Jeśli te wartości są sobie równie, to kolejka jest pełna i nie można dodać do niej nowego elementu 2 W takim wypadku na ekran wypisywany jest odpowiedni komunikat Jeśli jednak kolejka nie jest pełna, to funkcja najpierw zwiększa wartość indeksu last przy pomocy funkcji add_one(), a następnie zapisuje w elemencie tablicy określonym tą nową wartością indeksu wartość parametru data (wiersze 6 i 7) 2 Proszę porównać z odpowiednia ilustracją znajdującą się na poprzednich slajdach 50 / 60 Implementacja za pomocą tablicy - funkcja dequeue() 1 void dequeue(struct queue *fifo) 3 if(is_empty(*fifo)) 4 fprintf(stderr, "Kolejka jest pusta!\n"); 5 else 6 fifo->first = add_one(fifo->first); 7 } 51 / 60 Implementacja za pomocą tablicy - funkcja dequeue() Funkcja dequeue() w tej implementacji kolejki nie zwraca żadnej wartości, po prostu likwiduje początkowy element Najpierw jednak sprawdza, czy kolejka na której ma być przeprowadzona operacja nie jest pusta Jeśli okazałoby to się być nieprawdą, to funkcja wyświetla na ekranie odpowiedni komunikat i kończy swe działanie W przeciwnym przypadku usunięcie elementu sprowadza się do zwiększenia przy pomocy wywołania funkcji add_one() wartości indeksu first (wiersz nr 6) 52 / 60 Implementacja za pomocą tablicy - funkcja main() 1 int main(void) 3 int i; 4 make_empty(&fifo); 5 for(i=0;i<fifo_size-1;i++) 6 enqueue(&fifo,i); 7 while(!is_empty(fifo)) { 8 printf("%d ",first_one(fifo)); 9 dequeue(&fifo); 10 } 11 return 0; 12 } 53 / 60

Implementacja za pomocą tablicy - funkcja main() W funkcji main() programu kolejka jest najpierw inicjowana przy pomocy wywołania make_empty(), a następnie w pętli for dodawane są do niej elementy przy pomocy wywoływania funkcji enqueue() Kolejka może ich pomieścić tylko 19 Po zakończeniu pętli for wykonywana jest pętla while, gdzie wartości elementów kolejki są odczytywane za pomocą wywołania funkcji first_one() oraz usuwane z kolejki za pomocą wywołania funkcji dequeue() Tego typu implementacje kolejek fifo były realizowane w językach programowania, które nie zapewniały dynamicznego przydziału pamięci oraz w systemach, które nie posiadają dostatecznie dużo dostępnej pamięci operacyjnej (np w mikrokontrolerach) W ten sposób jest zorganizowany tzw bufor klawiatury, czyli miejsce, gdzie sterownik umieszcza informacje o naciśniętych przez użytkownika klawiszach, które następnie odczytuje procesor komputera Jest to przykład kolejki o ograniczonej pojemności, dodatkowo obsługiwanej sprzętowo 54 / 60 Testowanie operacji na strukturach danych Korzystanie ze zmiennych i struktur dynamicznych jest dosyć złożonym zadaniem Łatwo popełnić błędy implementując operacje na stosie, kolejce lub innej strukturze danych, a lokalizacja i usunięcia takich defektów może być trudnym przedsięwzięciem Istnieje jednak prosty sposób na przetestowanie funkcji implementujących operacje nie wymagające przydziału lub zwalniania pamięci, takie jak np print_queue() Wystarczy stworzyć kolejkę lub inną strukturę danych z elementów będących zwykłymi zmiennymi globalnymi lub lokalnymi i sprawdzić na niej działanie wymienionej funkcji W ograniczonym zakresie ten sposób można zastosować również do funkcji realizujących pozostałe operacje Funkcja znajdująca się na następnym slajdzie realizuje tego typu rozwiązanie 55 / 60 Testowanie operacji na strukturach danych 1 void print_queue_test(struct fifo_pointers *fifo) 3 struct fifo_node front, middle, rear; 4 5 frontdata = 1; 6 frontnext = &middle; 7 middledata = 2; 8 middlenext = &rear; 9 reardata = 3; 10 rearnext = NULL; 11 12 fifo->head = &front; 13 fifo->tail = &rear; 14 print_queue(*fifo); 15 fifo->head = fifo->tail = NULL; 16 } 56 / 60 Testowanie operacji na strukturach danych W funkcji print_queue_test() zadeklarowane są trzy zmienne typu struct fifo_node o nazwach front (przód), middle (środek) i rear (tył) Zostaną one umieszczone na pozycjach odpowiadających ich nazwom w trójelementowej kolejce Ta kolejka tworzona jest w wierszach 5-9, tzn inicjowane są pola data tych elementów oraz next Proszę zwrócić uwagę, ze wspomniane pola wskaźnikowe dwóch pierwszych elementów są inicjowane adresem kolejnego elementu w kolejce, a w trzecim, ostatnim elemencie umieszczana jest wartość null W wierszach 12 i 13 inicjowane są wskaźniki kolejki adresami pierwszego i ostatniego elementu W tej sposób powstaje kolejka, na której można testować działanie funkcji print_queue(), bez obawy, że ewentualne defekty w tej funkcji mogą uszkodzić strukturę kolejki i doprowadzić do wycieków pamięci Wiersz nr 15 zeruje wskaźniki kolejki, która jest tworzona ze zmiennych lokalnych funkcji, a w związku z tym po zakończeniu jej działania przestaje istnieć 57 / 60

Podsumowanie Kolejki implementowane w postaci dynamicznych struktur danych lub w oparciu o tablice mają liczne zastosowania Systemy operacyjne wykorzystują je do szeregowania procesów, realizacji specjalnych zmiennych nazywanych semaforami, organizacji zdań i wielu innych celów Podobnie zastosowania mają w oprogramowaniu współbieżnym, czy kompilatorach i innych programach Są również implementowane sprzętowo, jak wspomniany wcześniej bufor klawiatury Dosyć często, aby uprościć działanie i tworzenie kodu obsługującego kolejki programiści decydują się na stworzenie pojedynczego elementu kolejki, którego istnienie jest zawsze zagwarantowane Ten element jest zazwyczaj atrapą, tzn nie przechowuje istotnych danych i nazywany jest wartownikiem Kolejka zrealizowana z jego pomocą nazywa się kolejką z wartownikiem To rozwiązanie można zastosować również w przypadku stosu 58 / 60 Pytania? 59 / 60 koniec Dziękuję Państwu za uwagę! 60 / 60