Matematyka Dyskretna

advertisement
Matematyka Dyskretna
Andrzej Szepietowski
25 czerwca 2002 roku
Rozdział 1
Struktury danych
1.1 Listy, stosy i kolejki
Lista to uporza̧dkowany cia̧g elementów. Przykładami list sa̧ 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
array[1..N] of integer.
Jeżeli mamy zmienna̧ tego typu
a:array[1..N] of integer,
to tablica a zawiera N elementów
a[1], a[2], ... ,a[N].
W programie możemy odwoływać siȩ do całej tablicy, na przykład w instrukcji przypisania
a:=b,
lub do pojedynczych elementów:
a[i]:=a[i+1].
Możemy także używać tablic dwu lub wiȩcej wymiarowych. Przykładem tablicy dwuwymiarowej jest typ
array[1..N,1..M] of real.
Zmienna
c:array[1..N,1..M] of real
zawiera
elementów. Dla każdej pary liczb spełniaja̧cej warunki c[i,j] zawiera liczbȩ typu real.
, element
,
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, sa̧ kolejki i stosy.
3
4
Rozdział 1. Struktury danych
Definicja 1.2 Kolejka jest lista̧ z trzema operacjami:
dodawania nowego elementu na koniec kolejki,
zdejmowania pierwszego elementu z pocza̧tku 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 czekaja̧cy na obsłużenie tworza̧ kolejki.
Definicja 1.3 Stos jest lista̧ 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 sa̧ 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 organizuja̧ pracȩ na biurku. Przychodza̧ce listy układaja̧ na stosie i jak maja̧ czas, to
zdejmuja̧ jeden list i odpowiadaja̧ 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 pewna̧ ksia̧żkȩ).
Algorytm szukania ksia̧żki wśród znajomych
tworzymy STOS, który na pocza̧tku jest pusty,
wkładamy na STOS numer telefonu swojego znajomego,
powtarzamy dopóki na stosie sa̧ jakieś numery:
zdejmujemy z wierzchu STOSU jeden numer telefonu,
dzwonimy pod ten numer,
jeżeli osoba, do której siȩ dodzwoniliśmy, posiada szukana̧ ksia̧żkȩ, to koniec poszukiwań,
jeżeli nie posiada ksia̧żki, to pytamy ja̧ o numery telefonów jej znajomych,
którzy moga̧ mieć ksia̧żkȩ (lub znać kogoś kto ja̧ 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 hierarchiczna̧ struktura̧ danych. Jeden element drzewa, zwany korzeniem, jest
wyróżniony. Inne elementy drzewa sa̧ jego potomstwem lub potomstwem jego potomstwa
itd. Terminologia używana do opisu drzew jest mieszanina̧ 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 maja̧ce potomstwa. Drzewa czȩsto przedstawia siȩ w formie
grafu, gdzie każdy wierzchołek jest poła̧czony krawȩdzia̧ ze swoim ojcem i ze swoimi
dziećmi (swoim potomstwem). Dla każdego elementu w drzewie istnieje dokładnie jedna
ścieżka prowadza̧ca 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 ć cia̧gów
oznacza zbiór wszystkich skończonych cia̧gów zer i jedyzer i jedynek. Niech
nek. Zbiór ten zawiera cia̧g pusty (długości 0), oznaczany przez . Wierzchołki drzewa
oznaczamy w nastȩpuja̧cy sposób:
korzeń drzewa oznaczamy przez — pusty cia̧g,
jeżeli
jakiś wierzchołek jest oznaczony przez , to jego synowie oznaczeni sa̧ 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
nalezy: pójść w prawo do , potem znowu w prawo do , a na końcu w
lewo do
.
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ścia̧ drzewa nazywamy długość (liczb˛e kraw˛edzi) najdłuższej ścieżki w drzewie prowadza̧cej 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 sa̧ 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 sa̧ stałymi albo zmiennymi. Wierzchołki
nie bȩda̧ce liśćmi etykietowane sa̧ operacjami arytmetyczymi. Każdemu wierzchołkowi
w drzewie możemy przypisać wyrażenie arytmetyczne według nastȩpuja̧cej zasady:
dla liści wyrażeniami sa̧ etykiety tych liści (stałe lub zmienne),
jeżeli wierzchołek
wyrażenia
ma etykietȩ , a jego synom przypisano
, 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
Rysunek 1.3: Drzewo wyrażenia 7
)
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 logiczna̧ strukturȩ i sposób obliczania tego
wyrażenia.
8
Rozdział 1. Struktury danych
Istnieje sposób przedstawiania wyrażeń arytmetycznych nie wymagaja̧cy nawiasów.
Jest to tak zwana notacja polska lub Łukasiewicza. Jest ona też nazywana notacja̧ postfixowa̧,
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 postfixowa̧ postacia̧ 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 pomoca̧ nast˛epujacego
˛
algorytmu.:
Algorytm obliczania wartości wyrażenia w postaci postfixowej.
Dla kolejnych elementów zapisu wyrażenia:
jeżeli element jest stała̧ lub zmienna̧, 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,
obliczona̧ 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 maja̧ nastȩpuja̧ce wartości: , , , , .
Poniższa tabela przedstawia zawartość stosu po przeczytaniu kolejnych elementów wyrażenia.
1.4. Przeszukiwanie drzew binarnych
czytany element
a
b
c
d
e
9
stos
3,
3, 2,
3, 2, 1,
3, 3,
9,
9, 4,
9, 4, 2,
9, 2,
11.
1.4 Przeszukiwanie drzew binarnych
Zajmiemy siȩ teraz dwoma algorytmami przeszukiwania drzew (binarnych): przeszukiwanie w gła̧b i wszerz. Różnia̧ siȩ one rodzajem użytych struktur danych. W algorytmie
przeszukiwania w gła̧b użyjemy stosu, a w algorytmie przeszukiwania wszerz użyjemy
kolejki.
1.4.1 Przeszukiwanie drzewa w głab
˛
Algorytm przeszukiwania drzewa w gła̧b.
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 STOSU i zaznaczamy jako wierzchołek odwiedzony,
jeżeli takiego nie ma, to zdejmujemy
chołka bȩda̧cego na stosie pod spodem.
ze STOSU i cofamy siȩ do wierz-
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
01
0
,0,00
,0
,0,01
,0
,1
1
10
1
11
110
11
111
11
1
,1,10
,1
,1,11
,1,11,110
,1,11
,1,11,111
,1,11
,1
W metodzie przeszukiwania w gła̧b po każdym kroku algorytmu wierzchołki znajduja̧ce
siȩ na stosie tworza̧ ś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,
jeżeli przyszliśmy do wierzchołka od prawego syna odwiedzeni już byli obaj synowie.
(po zdjȩciu
Algorytm przeszukiwania drzewa w gła̧b (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
Jeżeli ostatnia̧ operacja̧ na stosi było włożenie nowego elementu, to:
Jeżeli
, to przejdź do
ale
i włóż 1 na stos,
i włóż 0 na stos,
, to przejdź do
ze stosu), to
(po zdjȩciu ze stosu), to
Oto prostsza wersja algorytmu przeszukiwania w gła̧b:
Jeżeli
1.4. Przeszukiwanie drzew binarnych
oraz
ojca wierzchołka .
Jeżeli 11
, to zdejmij ostatni element ze stosu i przejdź do
i włóż 1 na stos,
Jeżeli ostatnia̧ operacja̧ na stosie było zdjȩcie 0 to:
Jeżeli
chołka .
, to przejdź do
, to zdejmij ostatni element ze stosu i przejdź do ojca wierz-
Jeżeli ostatnia̧ operacja̧ 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
01
0
1
10
1
11
110
11
111
11
1
,0,0
,0
,0,1
,0
,1
,1,0
,1
,1,1
,1,1,0
,1,1
,1,1,1
,1,1
,1
Zauważmy, że etykiety na stosie zła̧czone razem tworza̧ 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 pocza̧tku 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
00,01
10,11
110,111
-
0,1
1,00,01
00,01,10,11
01,10,11
10,11
11
110,111
111
-
W metodzie przeszukiwania wszerz wierzchołki sa̧ przeszukiwane w kolejności od wierzchołków bȩda̧cych najbliżej wierzchołka pocza̧tkowego do wierzchołków bȩda̧cych 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ć postfixowa̧ wyrażenia, należy przeszukać drzewo tego wyrażenia w pewien określony sposób, zwany przeszukiwaniem postorder.
Przeszukiwanie postorder. Aby przeszukać (pod)drzewo maja̧ce 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 cia̧g:
który jest postacia̧ postfixowa̧ wyrażenia () .
1.5. Drzewa poszukiwań binarnych
13
Istnieja̧ jeszcze dwie inne pokrewne metody przeszukiwania drzew binarnych: inorder
i preorder:
Przeszukiwanie inorder. Aby przeszukać (pod)drzewo maja̧ce 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 maja̧ce 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 metoda̧ inorder, to etykiety utworza̧
cia̧g:
czyli wyrażenie w postaci infixowej, ale bez nawiasów. Przeszukanie tego samego drzewa
metoda̧ preorder da cia̧g 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 sa̧ podstawowa̧ struktura̧ przy budowie dużych baz danych. Jeda̧ z najprostszych
takich struktur sa̧ drzewa poszukiwań binarnych. Aby utworzyć drzewo poszukiwań binarnych, zaczynamy od pustego drzewa, a nastȩpnie wstawiamy po kolei elementy, które maja̧ być przechowywane w drzewie. Wstawiane elementy powinny być z jakiegoś
uporza̧dkowanego
zbioru. Poniżej przedstawiamy algorytmu wstawiania elementów do
drzewa. oznacza wartość przechowywana̧ 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ścia̧ korzenia :
jeżeli jeżeli , to wstaw do poddrzewa ,
, to wstaw do poddrzewa .
Przykład 1.11 Przypuśćmy, że mamy cia̧g liczb naturalnych:
#
! &
Utworzymy dla tego cia̧gu drzewo poszukiwań binarnych.
Rysunek 1.5: Drzewo poszukiwań po wstawieniu elementów: 128, 76, 106, 402
Po wstawieniu pierwszych czterech elementów cia̧gu otrzymamy drzewo, które jest
przedstawione na rysunku 1.5, a po wstawieniu całego cia̧gu otrzymamy drzewo, które
jest przedstawione na rysunku 1.6. Jeżeli teraz przeszukamy to drzewo metoda̧ inorder, to
otrzymamy ten sam cia̧g, ale uporza̧dkowany:
!
&
!
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 zawieraja˛ wartości mniejsze
od wartości w , a wszystkie wierzchołki w prawym poddrzewie zawieraja˛ 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 pomoca̧ poniższego algorytmu.
1.6. Zadania
15
Rysunek 1.6: Drzewo dla cia̧gu: 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ścia̧ :
jeżeli (
jeżeli jeżeli , to koniec, znaleźliśmy element na drzewie,
,
, to szukamy w prawym poddrzewie .
, to szukamy w lewym poddrzewie
W
drzewie poszukiwań binarnych czas wyszukiwania lub wstawiania elementu jest
, gdzie jest wysokościa̧ 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 metoda̧ „w gła̧b” („wszerz”) drzewo z rysunku 1.7.
3. Narysuj drzewo dla wyrażenie postfixowej i prefixowej.
. Przedstaw to wyrażenie w postaci
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ȩpuja̧cego cia̧gu liczb: 30, 43, 13, 8,
50, 40, 20, 19, 22.
7. Narysuj drzewo poszukiwań binarnych dla nastȩpuja̧cego cia̧gu 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]
krawȩdzi.
liściach ma wierzchołków
8. Udowodnij, że każde drzewo o werzchołkach ma 9. Udowodnij, że każde pełne drzewo binarne o 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).
Download