Matematyka Dyskretna Andrzej Szepietowski 25 czerwca 2002 roku
Rozdział 1 Struktury danych 1.1 Listy, stosy i kolejki Lista to uporz adkowany ci ag elementów. Przykładami list s a wektory lub tablice jednowymiarowe. W wektorach mamy dostȩp do dowolnego elementu, poprzez podanie indeksu tego elementu. Przykład 1.1 W jȩzyku Pascal przykładem typu tablicy jednowymiarowej jest Jeżeli mamy zmienn a tego typu to tablica a zawiera N elementów array[1..n] of integer. a:array[1..n] of integer, a[1], a[2],...,a[n]. W programie możemy odwoływać siȩ do całej tablicy, na przykład w instrukcji przypisania lub do pojedynczych elementów: a:=b, a[i]:=a[i+1]. Możemy także używać tablic dwu lub wiȩcej wymiarowych. Przykładem tablicy dwuwymiarowej jest typ Zmienna array[1..n,1..m] of real. c:array[1..n,1..m] of real zawiera elementów. Dla każdej pary liczb spełniaj acej warunki,, element c[i,j] zawiera liczbȩ typu real. Czasami wygodniej posługiwać siȩ listami bez używania indeksów. Przykładami list, których można używać bez konieczności odwoływania siȩ do indeksów poszczególnych elementów, s a kolejki i stosy. 3
4 Rozdział 1. Struktury danych Definicja 1.2 Kolejka jest list a z trzema operacjami: dodawania nowego elementu na koniec kolejki, zdejmowania pierwszego elementu z pocz atku kolejki, sprawdzania, czy kolejka jest pusta. Taki sposób dodawania i odejmowania elementów jest określany angielskim skrótem FIFO (first in first out, czyli pierwszy wszedł pierwszy wyjdzie). Przykłady kolejek spotykamy w sklepach, gdzie klienci czekaj acy na obsłużenie tworz a kolejki. Definicja 1.3 Stos jest list a z trzema operacjami: dodawania elementu na wierzch stosu, zdejmowania elementu z wierzchu stosu, sprawdzania, czy stos jest pusty. Na stosie dodajemy i odejmujemy elementy z tego samego końca, podobnie jak w stosie talerzy spiȩtrzonym na stole. Talerze dokładane s a na wierzch stosu i zdejmowane z wierzchu stosu. Taka organizacja obsługi listy określana jest angielskim skrótem LIFO (last in first out, czyli ostatni wszedł pierwszy wyjdzie). Niektórzy w ten sposób organizuj a pracȩ na biurku. Przychodz ace listy układaj a na stosie i jak maj a czas, to zdejmuj a jeden list i odpowiadaj a na niego. Przyjrzyjmy siȩ zastosowaniu kolejki lub stosu do szukania. Przypuśćmy, że szukamy przez telefon pewnej informacji (na przykład chcielibyśmy siȩ dowiedzieć, kto z naszych znajomych ma pewn a ksi ażkȩ). Algorytm szukania ksi ażki wśród znajomych tworzymy STOS, który na pocz atku jest pusty, wkładamy na STOS numer telefonu swojego znajomego, powtarzamy dopóki na stosie s a jakieś numery: zdejmujemy z wierzchu STOSU jeden numer telefonu, dzwonimy pod ten numer, jeżeli osoba, do której siȩ dodzwoniliśmy, posiada szukan a ksi ażkȩ, to koniec poszukiwań, jeżeli nie posiada ksi ażki, to pytamy j a o numery telefonów jej znajomych, którzy mog a mieć ksi ażkȩ (lub znać kogoś kto j a ma); każdy nowy numer zostaje dopisany do STOSU. W powyższym algorytmie zamiast stosu może być użyta kolejka.
1.2. Drzewa binarne 5 1.2 Drzewa binarne Drzewo jest hierarchiczn a struktur a danych. Jeden element drzewa, zwany korzeniem, jest wyróżniony. Inne elementy drzewa s a jego potomstwem lub potomstwem jego potomstwa itd. Terminologia używana do opisu drzew jest mieszanin a terminów z teorii grafów, botaniki i stosunków rodzinnych. Elementy drzewa nazywa siȩ wierzchołkami lub wȩzłami. Liście to wierzchołki nie maj ace potomstwa. Drzewa czȩsto przedstawia siȩ w formie grafu, gdzie każdy wierzchołek jest poł aczony krawȩdzi a ze swoim ojcem i ze swoimi dziećmi (swoim potomstwem). Dla każdego elementu w drzewie istnieje dokładnie jedna ścieżka prowadz aca od korzenia do tego wierzchołka. Drzewa binarne to takie drzewa, w których każdy wierzchołek ma co najwyżej dwóch synów. Do oznaczania wierzchołków w drzewie binarnym wygodnie jest używać ci agów zer i jedynek. Niech oznacza zbiór wszystkich skończonych ci agów zer i jedynek. Zbiór ten zawiera ci ag pusty (długości 0), oznaczany przez. Wierzchołki drzewa oznaczamy w nastȩpuj acy sposób: korzeń drzewa oznaczamy przez pusty ci ag, jeżeli jakiś wierzchołek jest oznaczony przez, to jego synowie oznaczeni s a przez i. Rysunek 1.1: Przykład drzewa binarnego Przy takim oznaczeniu wierzchołków drzewa binarnego nazwa wierzchołka mówi nam, jaka ścieżka prowadzi od korzenia do. Na przykład, aby dojść od korzenia do wierzchołka lewo do. nalezy: pójść w prawo do, potem znowu w prawo do, a na końcu w
6 Rozdział 1. Struktury danych Jeżeli mamy drzewo binarne, to z każdym wierzchołkiem możemy skojarzyć poddrzewo złożone z wierzchołka i wszystkich jego potomków. Na przykład w drzewie przedstawionym na rysunku 1.1 wierzchołek wyznacza poddrzewo przedstawione na rysunku 1.2. Rysunek 1.2: Poddrzewo Mówimy też, że drzewo składa siȩ z korzenia (wierzchołka ), z lewego poddrzewa i z prawego poddrzewa. Wysokości a drzewa nazywamy długość (liczbę krawędzi) najdłuższej ścieżki w drzewie prowadz acej od korzenia do liścia. Na przykład drzewo z rysunku 1.1 jest wysokości 3. 1.3 Drzewa wyrażeń arytmetycznych Przykładem zastosowania drzew binarnych s a drzewa wyrażeń arytmetycznych. Najpierw przykład. Na rysunku 1.3 przedstawiono drzewo wyrażenia. W drzewie tym każdy wierzchołek ma etykietȩ. Liście etykietowane s a stałymi albo zmiennymi. Wierzchołki nie bȩd ace liśćmi etykietowane s a operacjami arytmetyczymi. Każdemu wierzchołkowi w drzewie możemy przypisać wyrażenie arytmetyczne według nastȩpuj acej zasady: dla liści wyrażeniami s a etykiety tych liści (stałe lub zmienne), jeżeli wierzchołek ma etykietȩ, a jego synom przypisano wyrażenia, to wierzchołkowi przypisujemy wyrażenie! i. Przykład 1.4 W drzewie z rysunku1.3 wierzchołkowi z etykieta odpowiada wyrażenie, wierzchołkowi z etykieta wyrażenie ", a korzeniowi wyrażenie!'& # $%" Wyrażenie to zawiera wiȩcej nawiasów, niż to siȩ zwykle stosuje. Normalnie to samo wyrażenie przedstawiamy bez nawiasów w postaci ().
1.3. Drzewa wyrażeń arytmetycznych 7 Rysunek 1.3: Drzewo wyrażenia ) Opuszczenie nawiasów może prowadzić do niejednoznaczności lub może zmienić sens wyrażenia. Na przykład wyrażenie % po opuszczeniu nawiasów stanie siȩ identyczne z wyrażeniem "$" i zmieni sens. Drzewo, które odpowiada wyrażeniu %, przedstawiono na rysunku 1.4. Rysunek 1.4: Drzewo wyrażenia () Drzewo wyrażenia arytmetycznego oddaje logiczn a strukturȩ i sposób obliczania tego wyrażenia.
& 8 Rozdział 1. Struktury danych Istnieje sposób przedstawiania wyrażeń arytmetycznych nie wymagaj acy nawiasów. Jest to tak zwana notacja polska lub Łukasiewicza. Jest ona też nazywana notacj a postfixow a, ponieważ znak operacji stoi na końcu wyrażenia, za argumentami, czyli wyrażenie w notacji postfixowej ma postać: pierwszy argument drugi argument operacja. Notacja, do jakiej jesteśmy przyzwyczajeni, nazywa siȩ infixowa, ponieważ operacja znajduje siȩ pomiȩdzy argumentami, czyli wyrażenie w notacji infixowej ma postać: pierwszy argument operacja drugi argument. Przykład 1.5 Wyrażenie w postaci postfixowej! ma w postaci infixowej postać a wyrażenie jest postfixow a postaci a wyrażenia! () W wyrażeniach w postaci postfixowej nie potrzeba nawiasów. Wartość wyrażenia można w sposób jednoznaczny odtworzyć z samego wyrażenia za pomoc a następującego algorytmu.: Algorytm obliczania wartości wyrażenia w postaci postfixowej. Dla kolejnych elementów zapisu wyrażenia: jeżeli element jest stał a lub zmienn a, to wkładamy jego wartość na stos, jeżeli element jest znakiem operacji, to: zdejmujemy dwie wartości z wierzchu stosu, wykonujemy operacjȩ na tych wartościach, obliczon a wartość wkładamy na wierzch stosu, po przejściu całego wyrażenia jego wartość znajduje siȩ na stosie. Przykład 1.6 Zademonstrujmy ten algorytm na przykładzie wyrażenia: Załóżmy, że zmienne maj a nastȩpuj ace wartości:,,,,. Poniższa tabela przedstawia zawartość stosu po przeczytaniu kolejnych elementów wyrażenia. "
1.4. Przeszukiwanie drzew binarnych 9 czytany element stos a 3, b 3, 2, c 3, 2, 1, 3, 3, 9, d 9, 4, e 9, 4, 2, 9, 2, 11. 1.4 Przeszukiwanie drzew binarnych Zajmiemy siȩ teraz dwoma algorytmami przeszukiwania drzew (binarnych): przeszukiwanie w gł ab i wszerz. Różni a siȩ one rodzajem użytych struktur danych. W algorytmie przeszukiwania w gł ab użyjemy stosu, a w algorytmie przeszukiwania wszerz użyjemy kolejki. 1.4.1 Przeszukiwanie drzewa w głab Algorytm przeszukiwania drzewa w gł ab. Dane wejściowe: drzewo. odwiedzamy korzeń i wkładamy go na STOS; zaznaczamy jako wierzchołek odwiedzony, dopóki STOS nie jest pusty, powtarzamy: jeżeli jest wierzchołkiem na wierzchu STOSU, to sprawdzamy, czy istnieje syn wierzchołka, który nie był jeszcze odwiedzony, najpierw sprawdzamy, a potem. jeżeli takie siȩ znajdzie, to odwiedzamy, wkładamy go na wierzch STO- SU i zaznaczamy jako wierzchołek odwiedzony, jeżeli takiego nie ma, to zdejmujemy ze STOSU i cofamy siȩ do wierzchołka bȩd acego na stosie pod spodem. Przykład 1.7 Poniższa tabela pokazuje jaki wierzchołek jest odwiedzany i jaka jest zawartość stosu po każdej kolejnej iteracji pȩtli algorytmu, gdy przeszukiwane jest drzewo z rysunku 1.1.
10 Rozdział 1. Struktury danych Wierzchołek STOS 0,0 00,0,00 0,0 01,0,01 0,0 1,1 10,1,10 1,1 11,1,11 110,1,11,110 11,1,11 111,1,11,111 11,1,11 1,1 W metodzie przeszukiwania w gł ab po każdym kroku algorytmu wierzchołki znajduj ace siȩ na stosie tworz a ścieżkȩ od wierzchołka wejściowego do wierzchołka aktualnie odwiedzanego. Zauważmy, że nazwa każdego wierzchołka na stosie jest prefiksem (przedrostkiem) nazwy nastȩpnego wierzchołka. Dlatego wystarczy przechowywać ostatnie bity wierzchołków na stosie. Nie jest też konieczne zaznaczanie, które wierzchołki były już odwiedzone, wystarczy zauważyć, że: jeżeli przyszliśmy do wierzchołka od jego ojca, to żaden z synów nie był jeszcze odwiedzany, jeżeli przyszliśmy do wierzchołka od lewego syna odwiedzony był tylko lewy syn, (po zdjȩciu ze stosu), to jeżeli przyszliśmy do wierzchołka od prawego syna (po zdjȩciu ze stosu), to odwiedzeni już byli obaj synowie. Oto prostsza wersja algorytmu przeszukiwania w gł ab: Algorytm przeszukiwania drzewa w gł ab (druga wersja). Dane wejściowe: drzewo. odwiedzamy korzeń i wkładamy go na STOS, dopóki STOS nie jest pusty, powtarzamy: Jeżeli jest aktualnie odwiedzanym wierzchołkiem i Jeżeli ostatni a operacj a na stosi było włożenie nowego elementu, to: Jeżeli Jeżeli, to przejdź do i włóż 0 na stos, ale, to przejdź do i włóż 1 na stos,
Jeżeli ojca wierzchołka. 1.4. Przeszukiwanie drzew binarnych 11 oraz, to zdejmij ostatni element ze stosu i przejdź do Jeżeli ostatni a operacj a na stosie było zdjȩcie 0 to: Jeżeli, to przejdź do i włóż 1 na stos, Jeżeli, to zdejmij ostatni element ze stosu i przejdź do ojca wierzchołka. Jeżeli ostatni a operacj a na stosie było zdjȩcie 1 to: zdejmij ostatni element ze stosu i przejdź do ojca wierzchołka. Przykład 1.8 Poniższa tabela pokazuje jaki wierzchołek jest odwiedzany i jaka jest zawartość stosu po każdej kolejnej iteracji pȩtli drugiego algorytmu, gdy przeszukiwane jest drzewo z rysunku 1.1. Wierzchołek STOS 0,0 00,0,0 0,0 01,0,1 0,0 1,1 10,1,0 1,1 11,1,1 110,1,1,0 11,1,1 111,1,1,1 11,1,1 1,1 Zauważmy, że etykiety na stosie zł aczone razem tworz a nazwȩ aktualnie odwiedzanego wierzchołka. 1.4.2 Przeszukiwanie drzewa wszerz Nastȩpny algorytm przeszukiwania drzew używa kolejki jako pomocniczej struktury danych. Algorytm przeszukiwania wszerz. Dane wejściowe: drzewo. odwiedzamy korzeń drzewa i wkładamy go do KOLEJKI.
12 Rozdział 1. Struktury danych dopóki KOLEJKA nie jest pusta, powtarzamy: bierzemy jeden wierzchołek z pocz atku KOLEJKI, odwiedzamy wszystkiech synów wierzchołka kolejki. i wkładamy je na koniec Poniżej przedstawiono odwiedzane wierzchołki oraz zawartość kolejki po każdej kolejnej iteracji pȩtli algorytmu przeszukiwania wszerz drzewa przedstawionego na rysunku 1.1. wierzchołki KOLEJKA 0,1 0,1 00,01 1,00,01 10,11 00,01,10,11-01,10,11-10,11-11 110,111 110,111-111 - - W metodzie przeszukiwania wszerz wierzchołki s a przeszukiwane w kolejności od wierzchołków bȩd acych najbliżej wierzchołka pocz atkowego do wierzchołków bȩd acych dalej. 1.4.3 Rekurencyjne algorytmy przeszukiwania drzew Istnieje prosty i ciekawy sposób uzyskiwania postaci postfixowej wyrażenia arytmetycznego z drzewa tego wyrażenia. Aby uzyskać postać postfixow a wyrażenia, należy przeszukać drzewo tego wyrażenia w pewien określony sposób, zwany przeszukiwaniem postorder. Przeszukiwanie postorder. Aby przeszukać (pod)drzewo maj ace swój korzeń w wierzchołku : przeszukujemy jego lewe poddrzewo (z korzeniem w ), przeszukujemy jego prawe poddrzewo (z korzeniem w ), odwiedzamy wierzchołek (korzeń drzewa). Algorytm ten możemy krótko przedstawić w schemacie: lewe poddrzewo prawe poddrzewo korzeń. Przykład 1.9 Jeżeli przeszukamy drzewo z rysunku 1.4 i wypiszemy po kolei etykiety odwiedzanych wierzchołków, to otrzymamy ci ag: który jest postaci a postfixow a wyrażenia ().
1.5. Drzewa poszukiwań binarnych 13 Istniej a jeszcze dwie inne pokrewne metody przeszukiwania drzew binarnych: inorder i preorder: Przeszukiwanie inorder. Aby przeszukać (pod)drzewo maj ace swój korzeń w wierzchołku : przeszukujemy jego lewe poddrzewo (z korzeniem w ), odwiedzamy wierzchołek (korzeń drzewa), przeszukujemy jego prawe poddrzewo (z korzeniem w ). Przeszukiwanie preorder. Aby przeszukać (pod)drzewo maj ace swój korzeń w wierzchołku : odwiedzamy wierzchołek (korzeń drzewa), przeszukujemy jego lewe poddrzewo (z korzeniem w ), przeszukujemy jego prawe poddrzewo (z korzeniem w ). Przykład 1.10 Jeżeli przeszukamy drzewo z rysunku 1.4 metod a inorder, to etykiety utworz a ci ag: czyli wyrażenie w postaci infixowej, ale bez nawiasów. Przeszukanie tego samego drzewa metod a preorder da ci ag etykiet: ) Jest to tak zwana postać prefixowa wyrażenia. Znak operacji wystȩpuje w niej przed argumentami. Podobne jak w postaci postfixowej, postać prefixowa da siȩ jednoznacznie rozkładać i nie wymaga nawiasów. 1.5 Drzewa poszukiwań binarnych Drzewa s a podstawow a struktur a przy budowie dużych baz danych. Jed a z najprostszych takich struktur s a drzewa poszukiwań binarnych. Aby utworzyć drzewo poszukiwań binarnych, zaczynamy od pustego drzewa, a nastȩpnie wstawiamy po kolei elementy, które maj a być przechowywane w drzewie. Wstawiane elementy powinny być z jakiegoś uporz adkowanego zbioru. Poniżej przedstawiamy algorytmu wstawiania elementów do drzewa. oznacza wartość przechowywan a w wierzchołku. Pamiȩtajmy, że oznacza poddrzewo o korzeniu w wierzchołku. Algorytm wstawiania elementu do drzewa poszukiwań binarnych. Aby wstawić element do drzewa : jeżeli drzewo jest puste, to (wstaw do korzenia ),
& & 14 Rozdział 1. Struktury danych w przeciwnym razie porównaj z zawartości a korzenia jeżeli jeżeli, to wstaw do poddrzewa,, to wstaw do poddrzewa. : Przykład 1.11 Przypuśćmy, że mamy ci ag liczb naturalnych: #! Utworzymy dla tego ci agu drzewo poszukiwań binarnych. Rysunek 1.5: Drzewo poszukiwań po wstawieniu elementów: 128, 76, 106, 402 Po wstawieniu pierwszych czterech elementów ci agu otrzymamy drzewo, które jest przedstawione na rysunku 1.5, a po wstawieniu całego ci agu otrzymamy drzewo, które jest przedstawione na rysunku 1.6. Jeżeli teraz przeszukamy to drzewo metod a inorder, to otrzymamy ten sam ci ag, ale uporz adkowany:!! Jeżeli mamy już drzewo poszukiwań binarnych, to dla każdego wierzchołka zachodzi dla każdego,, dla każdego,. Czyli wszystkie wierzchołki w lewym poddrzewie zawierają wartości mniejsze od wartości w, a wszystkie wierzchołki w prawym poddrzewie zawierają wartości mniejsze od wartości w. Aby stwierdzić, czy jakiś element znajduje siȩ na tym drzewie. Postȩpujemy podobnie jak przy wstawianiu elementów. Zaczynamy od korzenia drzewa i szukamy elementu za pomoc a poniższego algorytmu.
1.6. Zadania 15 Rysunek 1.6: Drzewo dla ci agu: 128,76,106,402,100,46,354,1018,112,28, 396,35 Algorytm szukania elementu na drzewie. Aby stwierdzić, czy element znajduje siȩ na drzewie : jeżeli jest puste, to koniec, elementu nie ma na drzewie, jeżeli nie jest puste, to porównujemy z wartości a : jeżeli (, to koniec, znaleźliśmy element na drzewie, jeżeli, to szukamy w lewym poddrzewie, jeżeli, to szukamy w prawym poddrzewie. W drzewie poszukiwań binarnych czas wyszukiwania lub wstawiania elementu jest, gdzie jest wysokości a drzewa. W obu algorytmach tylko raz przechodzimy od korzenia w dół do liścia. Najlepiej by było, gdyby wysokość drzewa była rzȩdu logarytm od liczby wierzchołków, ale nie w każdym drzewie poszukiwań binarnych tak musi być. 1.6 Zadania 1. Ile wierzchołków może mieć drzewo binarne wysokości? 2. Przeszukaj metod a w gł ab ( wszerz ) drzewo z rysunku 1.7. 3. Narysuj drzewo dla wyrażenie. Przedstaw to wyrażenie w postaci postfixowej i prefixowej.
16 Rozdział 1. Struktury danych 4. Narysuj drzewo dla wyrażenie. Przedstaw to wyrażenie w postaci infixowej i prefixowej. Oblicz wartość tego wyrażenia. Przedstaw to wyrażenie w postaci infixowej i prefixowej. 5. Wypisz w postaci infixowej, prefixowej i postfixowej wyrażenie przedstawione na rysunku 7. Rysunek 1.7: Drzewo wyrażenia 6. Narysuj drzewo poszukiwań binarnych dla nastȩpuj acego ci agu liczb: 30, 43, 13, 8, 50, 40, 20, 19, 22. 7. Narysuj drzewo poszukiwań binarnych dla nastȩpuj acego ci agu słów: słowik, wróbel, kos, jaskółka, kogut, dziȩcioł, gil, kukułka, szczygieł, sowa, kruk, czubatka. [Fragment wiersza Ptasie radio Juliana Tuwima] 8. Udowodnij, że każde drzewo o werzchołkach ma krawȩdzi. 9. Udowodnij, że każde pełne drzewo binarne o liściach ma wierzchołków wewnȩtrznych. Wskazówka. Drzewo binarne nazywa siȩ pełne, jeżeli każdy jego wierzchołek ma albo dwóch synów, albo nie ma synów wcale (jest liściem).