You are on page 1of 8

Politechnika Świętokrzyska

w Kielcach
Wydział Elektrotechniki, Automatyki i Informatyki

Podstawy programowania 2

Instrukcja laboratoryjna 4

„Stos i kolejka FIFO”

Przygotowali:
mgr inż. Paweł Pięta
dr inż. Arkadiusz Chrobot

Kielce, 2023
1 Wstęp
Celem zajęć jest wprowadzenie pojęć abstrakcyjnego typu i struktury danych oraz
praktyczne zapoznanie się z dynamicznymi strukturami danych stosu i kolejki.

2 Abstrakcyjne typy danych


Oprócz używania podstawowych typów danych stanowiących część języka programo-
wania, możliwe jest także tworzenie bardziej skomplikowanych struktur danych. Jeśli zo-
stanie im przydzielona pamięć na stercie, wówczas będziemy je nazywać dynamicznymi
strukturami danych. Jeżeli dodatkowo określimy możliwe do wykonania na nich opera-
cje oraz przechowywane przez nie dane, zdefiniujemy wtedy abstrakcyjny typ danych.
Struktura opisana takim typem będzie nosić miano abstrakcyjnej struktury danych.

3 Stos
Pierwsza abstrakcyjna struktura danych, która zostanie omówiona w ramach tej in-
strukcji, to stos (ang. stack). Dane w niej przechowywane zarządzane są zgodnie z zasadą
Ostatni Nadszedł Pierwszy Wychodzi (ang. Last In First Out, w skrócie LIFO). Jednym
ze sposobów jej implementacji jest dynamiczna struktura elementów powiązanych ze sobą
za pomocą wskaźników, posiadająca początek i koniec. Przykład definicji typu bazowego
dla dynamicznej struktury stosu, przechowującej liczby typu int, został przedstawiony
w kodzie źródłowym 1 w liniach 5–9. Podstawowe operacje, które będą wykonywane na
strukturze stosu, to:

1. dodanie nowego elementu do stosu – operacja push,


2. usunięcie elementu ze stosu – operacja pop,
3. odczytanie zawartości elementu szczytowego stosu – operacja peek.

Będą one przeprowadzane zawsze na końcu stosu, inaczej nazywanym szczytem (ang.
top). Implementacja funkcji push(), pop() i peek() została zaprezentowana w kodzie
źródłowym 1 w liniach 11–44. Ponadto funkcja push(), która jest zrealizowana z uży-
ciem podwójnego wskaźnika na koniec stosu, została pokazana w kodzie źródłowym 2.
Aby funkcje te zadziałały poprawnie, przekazany im przez parametr szczyt stosu musi
wskazywać na istniejący lub pusty stos.
Stos może zostać również zaimplementowany na bazie tablicy. Przykład struktury
stosu dla tej implementacji został przedstawiony w kodzie źródłowym 3. Maksymalny
rozmiar stosu został zdefiniowany stałą STACK_SIZE. Pole top struktury stack będzie
natomiast określać indeks tablicy data, pod którym znajduje się aktualny szczyt stosu.
Dla pustego stosu pole top będzie miało wartość -1.

2
Kod źródłowy 1: Przykładowy program ze stosem

1 # include < stdio .h >


2 # include < stdlib .h >
3 # include < time .h >
4
5 struct stack_node
6 {
7 int data ;
8 struct stack_node * next ;
9 };
10
11 struct stack_node * push ( struct stack_node * top , int data )
12 {
13 struct stack_node * new_node = ( struct stack_node *)
14 malloc ( sizeof ( struct stack_node ) ) ;
15 if ( NULL != new_node )
16 {
17 new_node - > data = data ;
18 new_node - > next = top ;
19 top = new_node ;
20 }
21 return top ;
22 }
23
24 int pop ( struct stack_node ** top )
25 {
26 int result = -1;
27 if ( NULL != * top )
28 {
29 result = (* top ) -> data ;
30 struct stack_node * tmp = (* top ) -> next ;
31 free (* top ) ;
32 * top = tmp ;
33 }
34 return result ;
35 }
36
37 int peek ( struct stack_node * top )
38 {
39 if ( NULL != top )
40 return top - > data ;
41
42 fprintf ( stderr , " Stack is empty .\ n " ) ;
43 return -1;
44 }
45
46 int main ()
47 {
48 struct stack_node * top = NULL ;
49 srand ( time ( NULL ) ) ;
50 int i , elements = 5+ rand () %6;
51
52 printf ( " Top of the stack : % d \ n " , peek ( top ) ) ;
53 for ( i =0; i < elements ; i ++)
54 top = push ( top , i ) ;
55 printf ( " Top of the stack : % d \ n " , peek ( top ) ) ;
56 printf ( " Stack elements : " ) ;

3
57 while ( NULL != top )
58 printf ( " % d " , pop (& top ) ) ;
59 printf ( " \ n " ) ;
60 printf ( " Top of the stack : % d \ n " , peek ( top ) ) ;
61 return 0;
62 }

Kod źródłowy 2: Implementacja funkcji push() z użyciem podwójnego wskaźnika

1 void push ( struct stack_node ** top , int data )


2 {
3 struct stack_node * new_node = ( struct stack_node *)
4 malloc ( sizeof ( struct stack_node ) ) ;
5 if ( NULL != new_node )
6 {
7 new_node - > data = data ;
8 new_node - > next = * top ;
9 * top = new_node ;
10 }
11 }

Kod źródłowy 3: Typ bazowy implementacji stosu na bazie tablicy

1 # define STACK_SIZE 10
2
3 struct stack
4 {
5 int data [ STACK_SIZE ];
6 int top ;
7 };

4 Kolejka FIFO
Kolejna abstrakcyjna struktura danych, która zostanie omówiona w ramach tej instruk-
cji, to kolejka (ang. queue). Dane w niej przechowywane zarządzane są zgodnie z zasadą
Pierwszy Nadszedł Pierwszy Wychodzi (ang. First In First Out, w skrócie FIFO). Jednym
ze sposobów jej implementacji jest dynamiczna struktura elementów powiązanych ze sobą
za pomocą wskaźników, posiadająca początek i koniec. Przykład definicji typu bazowego
dla dynamicznej struktury kolejki, przechowującej liczby typu int, został przedstawiony
w kodzie źródłowym 4 w liniach 6–10. Podstawowe operacje, które będą wykonywane na
strukturze kolejki, to:

1. dodanie nowego elementu do kolejki – operacja enqueue,


2. usunięcie elementu z kolejki – operacja dequeue,
3. wypisanie zawartości elementów kolejki – operacja print,
4. przeszukiwanie liniowe kolejki – operacja search.

4
Do obsługi kolejki wykorzystuje się dwa wskaźniki: jeden wskazujący na początek ko-
lejki, inaczej nazywany czołem lub głową (ang. head), a drugi na jej koniec, określany mia-
nem ogona (ang. tail). Do ich przechowywania posłuży w programie dodatkowa struktura
zdefiniowana w kodzie źródłowym 4 w liniach 12–15. Operacja enqueue będzie przepro-
wadzana zawsze na końcu kolejki FIFO, natomiast operacja dequeue będzie się odbywać
zawsze na jej początku. Implementacja funkcji enqueue() i dequeue() została pokazana
w kodzie źródłowym 4 w liniach 17–50. Aby funkcje te zadziałały poprawnie, wskaźniki
na początek i koniec kolejki, będące polami przekazanej im przez parametr struktury,
muszą wskazywać na istniejącą kolejkę lub być puste. Funkcje zwracają wartość true jeśli
operacja powiodła się, w przeciwnym razie wartość false.
W celu uproszczenia kodu zarządzającego kolejką możliwe jest wykorzystanie tzw.
wartownika (ang. guard lub sentinel), czyli pojedynczego, specjalnego elementu, którego
istnienie jest zawsze zagwarantowane, nawet gdy kolejka jest pusta. Znajduje się on na jej
końcu i nie przechowuje żadnych istotnych danych, a jego pole next zazwyczaj wskazuje
na niego samego. Typ bazowy kolejki z wartownikiem jest taki sam jak zwykłej kolejki,
natomiast dodatkowa struktura, która przechowuje wskaźniki na początek i koniec kolejki,
będzie również zawierać wskaźnik na element wartownika, co zostało pokazane w kodzie
źródłowym 5. Dla pustej kolejki wszystkie trzy wskaźniki będą wskazywać na element
wartownika.
Podczas dodawania nowego elementu do kolejki z wartownikiem należy pamiętać, aby
po wykonaniu operacji za ogonem kolejki nadal znajdował się wartownik. Taka implemen-
tacja struktury kolejki upraszcza np. algorytm przeszukiwania liniowego: szukana wartość
zapisywana jest w wartowniku, dzięki czemu w każdej iteracji pętli wykonywane jest tylko
jedno porównanie, tj. wartości obecnego elementu kolejki do szukanej wartości. Funkcje
realizujące przeszukiwanie liniowe zwykłej kolejki oraz kolejki z wartownikiem zostały
zaprezentowane w kodzie źródłowym 6.

Kod źródłowy 4: Przykładowy program z kolejką FIFO

1 # include < stdio .h >


2 # include < stdlib .h >
3 # include < stdbool .h >
4 # include < time .h >
5
6 struct queue_node
7 {
8 int data ;
9 struct queue_node * next ;
10 };
11
12 struct queue_pointers
13 {
14 struct queue_node * head , * tail ;
15 };
16

5
17 bool enqueue ( struct queue_pointers * queue , int data )
18 {
19 struct queue_node * new_node = ( struct queue_node *)
20 malloc ( sizeof ( struct queue_node ) ) ;
21 if ( NULL != new_node )
22 {
23 new_node - > data = data ;
24 new_node - > next = NULL ;
25 if ( NULL == queue - > head )
26 queue - > head = queue - > tail = new_node ;
27 else
28 {
29 queue - > tail - > next = new_node ;
30 queue - > tail = new_node ;
31 }
32 return true ;
33 }
34 return false ;
35 }
36
37 bool dequeue ( struct queue_pointers * queue , int * data )
38 {
39 if ( NULL != queue - > head )
40 {
41 struct queue_node * tmp = queue - > head - > next ;
42 * data = queue - > head - > data ;
43 free ( queue - > head ) ;
44 queue - > head = tmp ;
45 if ( NULL == tmp )
46 queue - > tail = NULL ;
47 return true ;
48 }
49 return false ;
50 }
51
52 void print_queue ( struct queue_pointers queue )
53 {
54 for (; NULL != queue . head ; queue . head = queue . head - > next )
55 printf ( " % d " , queue . head - > data ) ;
56 printf ( " \ n " ) ;
57 }
58
59 int main ()
60 {
61 struct queue_pointers queue = { NULL , NULL };
62 srand ( time ( NULL ) ) ;
63 int i , elements = 5+ rand () %6;
64
65 for ( i =0; i < elements ; i ++)
66 enqueue (& queue , i ) ;
67 printf ( " Queue elements : " ) ;
68 print_queue ( queue ) ;
69 printf ( " Removing queue : " ) ;
70 while ( dequeue (& queue , & i ) )
71 printf ( " % d " , i ) ;
72 return 0;
73 }

6
Kod źródłowy 5: Utworzenie kolejki z wartownikiem

1 # include < stdio .h >


2 # include < stdlib .h >
3
4 struct queue_node
5 {
6 int data ;
7 struct queue_node * next ;
8 };
9
10 struct queue_with_guard
11 {
12 struct queue_node * head , * tail , * guard ;
13 };
14
15 int main ()
16 {
17 struct queue_node guard ;
18 guard . next = & guard ;
19 struct queue_with_guard queue = {& guard , & guard , & guard };
20 return 0;
21 }

Kod źródłowy 6: Przeszukiwanie liniowe zwykłej kolejki oraz kolejki z wartownikiem

1 struct queue_node * search ( struct queue_pointers queue , int data )


2 {
3 for (; NULL != queue . head ; queue . head = queue . head - > next )
4 if ( queue . head - > data == data )
5 return queue . head ;
6 return NULL ;
7 }
8
9 struct queue_node * searc h_wit h_guar d ( struct queue_with_guard queue ,
10 int data )
11 {
12 queue . guard - > data = data ;
13 for (; queue . head - > data != data ; queue . head = queue . head - > next ) ;
14 if ( queue . head != queue . guard )
15 return queue . head ;
16 return NULL ;
17 }

5 Zadania
Uwaga! Programy należy napisać z podziałem na funkcje z parametrami oraz nie
można w nich korzystać ze zmiennych globalnych.

1. Napisz program, w którym zaimplementujesz stos na bazie tablicy. Liczbę elementów


tablicy dobierz samodzielnie. Przetestuj tę implementację. Twój program powinien
wykrywać sytuacje kiedy stos jest pusty, a kiedy pełny.

7
2. Napisz program, w którym użyjesz stosu do sprawdzania poprawności parowania na-
wiasów w wyrażeniach arytmetycznych. Przykładowo, jeśli w wyrażeniu nawiasy są
w następującym porządku (()()), to ich parowanie jest poprawne, a jeśli w porządku
)()(, to ich parowanie nie jest poprawne.

3. Zmień definicję funkcji push(), tak aby zwracała ona wartość typu bool sygnali-
zującą, czy udało się dodać nowy element do stosu. Wskaźnik stosu powinien być
modyfikowany przy pomocy parametru.

4. Stwórz bibliotekę, która będzie pozwalała programom tworzyć stos jako dynamiczną
strukturę danych.

5. Napisz program, który będzie losował liczby naturalne z zakresu [0, 10] i odkładał je
na dwa osobne stosy, w zależności od tego, czy wylosowana liczba jest parzysta, czy
nieparzysta. Program powinien następnie usunąć oba stosy i wypisać ich zawartość.

6. Napisz program, w którym zaimplementujesz stos pozwalający szybko odszukać


element o najmniejszej wartości. Możesz to uzyskać dodając pole wskaźnikowe do
typu bazowego stosu i stosując dodatkowy wskaźnik stosu oraz modyfikując funkcje
push() i pop().

7. Napisz program, w którym zaimplementujesz kolejkę FIFO przechowującą łańcuchy


znaków.

8. Napisz program, w którym zaimplementujesz kolejkę FIFO i funkcję rekurencyjną,


która będzie służyła do wypisywania wartości elementów tej kolejki na ekran.

9. Napisz program, który zapamięta podane przez użytkownika liczby typu float w ko-
lejce (jedna liczba – jeden element kolejki), a następnie wypisze te liczby na ekran
i policzy ich średnią, usuwając przy tym kolejkę.

10. Napisz program, w którym zaimplementujesz kolejkę FIFO z wartownikiem.

You might also like