Professional Documents
Culture Documents
w Kielcach
Wydział Elektrotechniki, Automatyki i Informatyki
Podstawy programowania 2
Instrukcja laboratoryjna 4
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.
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:
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
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 }
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:
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.
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
5 Zadania
Uwaga! Programy należy napisać z podziałem na funkcje z parametrami oraz nie
można w nich korzystać ze zmiennych globalnych.
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ść.
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ę.