PODSTAWY INFORMATYKI wykład 6. Adrian Horzyk Web: http://home.agh.edu.pl/~horzyk/ E-mail: horzyk@agh.edu.pl Google: Adrian Horzyk Gabinet: paw. D13 p. 325 Akademia Górniczo-Hutnicza w Krakowie WEAIiE, Katedra Automatyki http://www.agh.edu.pl Al. Mickiewicza 30, 30-059059 Kraków
Drzewa Drzewa to rodzaj struktury danych, w której możemy wyróżnić: korzeń (wierzchołek główny) nie ma rodzica/przodka wierzchołki (wewnętrzne) (węzły), które połączone są krawędziami i mogą posiadać: poprzedniki (przodków, rodziców bezpośrednie poprzedniki) następniki (potomków, dzieci bezpośrednie następniki) liście (nie posiadają już następników) Każdy wierzchołek może posiadać dowolną ilość dzieci: drzewa binarne posiadają maksymalnie dwa drzewa wyższych rzędów (np. AVL, czerwono-czarne, BST) mogą posiadać więcej niż dwóch potomków posiada dokładnie jednego rodzica Ścieżka w drzewie to droga (ciąg krawędzi) prowadząca między dowolnymi dwoma wierzchołkami w drzewie (istnieje zawsze tylko jedna) Liczba krawędzi tworzących ścieżkę nazywana jest jej długością. Poziom węzła w drzewie wyznacza 1+długość ścieżki łączącej korzeń drzewa z węzłem. Wysokość drzewa wyznaczona jest przez maksymalny poziom wszystkich jego węzłów. Las to kilka drzew powiązanych ze sobą razem. Wykład 6. Strona 2.
Definicje porządku i operacje na drzewie Przeszukiwanie drzewa to operacja przechodzenia kolejnych wierzchołków zgodnie z zależnościami rodzic-dziecko. Drzewa można przeszukiwać w porządku: - Preorder (wszerz) gdy działania przeprowadzamy najpierw na rodzicu a potem na dzieciach, a następnie na dzieciach dzieci itd. - Postorder (wgłąb) gdy działania przeprowadzamy najpierw kolejno na wszystkich dzieciach i dzieciach dzieci itd. a na końcu na rodzicu. - Inorder gdy działanie przeprowadzamy najpierw na jednym dziecku, potem na rodzicu a potem na drugim dziecku - charakterystyczna dla drzew binarnych. wysokość drzewa głębokość węzła Wykład 6. Strona 3.
Algorytmy przeszukiwania drzew Dwa klasyczne algorytmy przeszukiwania drzew:: BFS breadthfirstsearch przeszukiwanie wszerz DFS depthfirstsearch przeszukiwanie wgłąb Efektywność poszczególnych sposobów przeszukiwania drzew jest zależna od rozwiązywanego problemu. 1 BFS 1 DFS 2 3 4 5 6 2 6 7 11 12 7 8 9 10 11 12 13 3 5 8 9 13 17 18 14 15 16 17 18 19 4 10 14 15 16 19 Wykład 6. Strona 4.
Drzewo poszukiwań z wartownikiem Jako ostatni węzeł (liść) drzewa ustawiamy zawsze liść, którego pełni rolę wartownika w drzewie. Wskaźnik KORZEŃ Wsk 1.Wsk. 2 1. 2. Wsk 3.Wsk 4. Wsk 5.Wsk 6. 3. wart. wart. 4. Wsk 7.Wsk 8. 5. wart. wart. 6. wart. wart. 7. wart. wart. 8. wart. wart. wartownik Wykład 6. Strona 5.
Drzewa binarne Drzewa binarne to taki rodzaj drzew, w których każdy węzeł posiada maksymalnie dwa następniki (dwóch potomków). Stopień każdego wierzchołka jest nie większy niż 2. Wskaźnik KORZEŃ Wsk 1.Wsk. 2 1. Wsk 3.Wsk 4. 2. Wsk 5.Wsk 6. 3. 4. Wsk 7.Wsk 8. 5. 6. 7. 8. Drzewa binarne często implementujemy z wykorzystaniem tablicy: KORZ. 1. 2. 3. 4. 5. 6. puste puste 7. 8. puste puste Wykład 6. Strona 6.
Budowa drzewa z wykorzystaniem wskaźników Wskaźnik KORZEŃ Wsk 1.Wsk. 2 1. Wsk 3.Wsk 4. 2. Wsk 5.Wsk 6. 3. 4. Wsk 7.Wsk 8. 5. 6. 7. 8. Wykład 8. Strona 7.
Drzewa arytmetyczne Drzewa arytmetyczne to drzewa (zwykle binarne) wykorzystane do zapisu operacji matematycznych, tj. +, -, *, / itp. odwzorowując kolejność wykonywania działań. W takich drzewach operacje matematyczne umieszczone są w węzłach drzewa, zaś atrybuty (parametry) w liściach. Wskaźnik (a + b / c) * (d e * f) KORZEŃ * Wsk 1.Wsk. 2 1. + Wsk 3.Wsk 4. 2. - Wsk 5.Wsk 6. 3. a 4. / Wsk 7.Wsk 8. 5. d 4. / Wsk 7.Wsk 8. 7. b 8. c 7. b 8. c Wykład 6. Strona 8.
Sortowanie przez kopcowanie KORZ. 1. 2. 3. 4. 5. 6. 7. zawart 8. zawart 9. 10. Wykład 8. Strona 9.
Drzewa decyzyjne Każdy węzeł w drzewie decyzyjnym ma etykietę a i : a j dla pewnych i oraz j z przedziału 1 i, j n, gdzie n jest liczbą elementów w ciągu wejściowym. Z każdym liściem związana jest jedna permutacja π(1), π(2),..., π(n). Wykonanie algorytmu sortującego odpowiada przejściu ścieżki od korzenia drzewa decyzyjnego do jednego z jego liści, tj. znalezieniu poprawnej permutacji elementów ciągu. W każdym węźle wewnętrznym zostaje wykonane porównanie typua i a j. W lewym poddrzewie znajdują się porównania wykonywane przez algorytm, jeśli okaże się, że a i a j, a prawe poddrzewo zawiera możliwe scenariusze dla przeciwnego przypadku, tj. a i > a j. Jeśli algorytm sortujący działa poprawnie, to każda z n! permutacji musi wystąpić jako jeden z liści drzewa decyzyjnego. Długość najdłuższej ścieżki od korzenia drzewa decyzyjnego do dowolnego z jego liści odpowiada pesymistycznej liczbie porównań wykonywanych przez algorytm sortujący. Wszystkie algorytmy sortowania za pomocą porównań można prezentować w postaci drzew decyzyjnych! Każde drzewo decyzyjne, odpowiadające algorytmowi poprawnie sortującemu n elementów, ma wysokość Ω(n log n). Wykład 6. Strona 10.
Drzewa BST Binarne drzewo poszukiwań (BST, BinarySearchTree) to drzewo binarne stosowane do szybkiego wyszukiwania. Drzewa BST często służą do modelowania bardziej abstrakcyjnych struktur danych, np. zbiorów czy słowników. W każdym z węzłów drzewa BST przechowywany jest unikatowy klucz, którego wartość klucza jest zawsze nie mniejsza niż wartości wszystkich kluczy z lewego poddrzewa, a nie większa niż wartości wszystkich kluczy z prawego poddrzewa. Relacje niemniejszości i niewiększości można zamienić odpowiednio na relacje większości i mniejszości, ale ograniczamy się wówczas do przypadku unikalności kluczy (wykluczamy klucze, które mogą się powtarzać). Przechodząc drzewo metodą inorder, uzyskuje się ciąg wartości posortowanych niemalejąco (lub rosnąco w przypadku unikatowych kluczy). Pesymistyczny koszt każdej z operacji na drzewie BST (o liczbie węzłów n), tj. wyszukiwania, dodania lub usunięcia klucza, zależy od wysokości drzewa i wynosi: log 2 n dla drzewa zrównoważonego (najlepszy przypadek) n dla drzewa zdegenerowanego do listy, tj. takiego, w którym każdy z węzłów oprócz liścia ma tylko jednego potomka. Wykład 6. Strona 11.
Drzewa BST program BST; uses crt; type wsk = ^wezel; wezel = record d : integer; l, r : wsk; var n,i,x : integer; p : wsk; poz : byte; procedure Wstaw (var p : wsk; x : integer); if p = nil then new(p); p^.d := x; p^.l := nil; p^.r := nil; end else if x<p^.d then Wstaw (p^.l,x) else Wstaw (p^.r,x); function Licz (p : wsk) : integer; var k : integer; if p <> nil then k := 1; if p^.l <> nil then k := k+licz (p^.l); if p^.r <> nil then k := k+licz (p^.r); Licz := k; end else Licz := 0; procedure Pokaz (p : wsk); writeln(p^.d); if p^.l <> nil then Pokaz (p^.l); if p^.r <> nil then Pokaz (p^.r); Wykład 6. Strona 12.
Drzewa BST procedure Wywaz (var p : wsk; b : integer); var a : integer; q, w : wsk; b := b-1; a := Licz(p^.l); b := b-a; while abs(a-b) > 1 do if a > b then if p^.l^.r <> nil then q := p^.l; repeat w := q; q := q^.r; until q^.r = nil; q^.r := p; q^.l := p^.l; w^.r := nil; p^.l := nil; p := q; end else p^.l^.r := p; q := p^.l; p^.l := nil; p := q; a := a-1; b := b+1; end else if p^.r^.l <> nil then q := p^.r; repeat w := q; q := q^.l; until q^.l=nil; q^.l := p; q^.r := p^.r; w^.l := nil; p^.r := nil; p := q; end else p^.r^.l := p; q := p^.r; p^.r := nil; p := q; a := a+1; b := b-1; if p^.l <> nil then Wywaz(p^.l,a); if p^.r <> nil then Wywaz(p^.r,b); p := nil; clrscr; writeln; write('ile elementow? '); readln(n); for i := 1 to n do write('element numer ',i,': '); readln(x); Wstaw(p,x); writeln; writeln('nie wywazone:'); Pokaz(p); n := Licz(p); writeln('elementow jest ',n); Wywaz(p,n); writeln; writeln('wywazone:'); Pokaz(p); readln; end. Wykład 6. Strona 13.
Operacje na wskaźnikach (dynamiczne struktury danych) Wskaźnik (pointer, zmienna wskaźnikowa) to rodzaj zmiennej przechowującej adres miejsca w pamięci, pod którym może być przechowywane jakaś dane określonego typu i rozmiaru. Wskaźnik wskazuje miejsce, gdzie taka struktura danych rozpoczyna się w pamięci RAM i zwykle określony zostaje również typ danych, na jaki wskazuje. VAR a : Integer; // deklaracja (gdzieś w pamięci) zmiennej a typu Integer Kompilator ukrywa przed nami adres przechowywania w pamięci zwykłej zmiennej nadając temu adresowi pewną nazwę a. Nie mamy też możliwości zmiany miejsca pamięci tej zmiennej ani zmiany adresu na jaki wskazuje zmienna a. a := 100; // przyporządkowanie wartości 100 zmiennej a (gdzieś tam w pamięci) VAR p : ^Integer; // deklaracja wskaźnika typu Integer bez nadania mu wartości! Wskaźnik p może wskazywać na pewne miejsce w pamięci RAM. Miejsce, na które ma wskazywać zwykle otrzymujemy jako parametr zwrotny np. z procedury new (p). Procedura ta rezerwuje miejsce w pamięci RAM do przechowywania danych typu Integer (tak samo jak deklaracja VAR a:integer) i do zmiennej wskaźnikowej p zapisuje adres, gdzie tą pamięć zarezerwowała. Następnie tak samo jak dla zmiennej a trzeba temu miejscu w pamięci przypisać jakąś wartość: p^ := 100; // przyporządkowanie wart. i 100 miejscu w pamięci wskazywanemu przez p Wskaźniki mają tą przewagę nad zwykłymi zmiennymi, iż zmieniać adres, na który wskazują, a dzięki temu nawet kilka wskaźników może wskazywać ten sam adres: r := p; Wykład 6. Strona 14.
Kopiec Kopiec to drzewo binarne, w węzłach którego znajdują się elementy reprezentowanego multizbioru S i jest spełniony tzw. warunek kopca, mianowicie: jeśli węzeł x jest następnikiemwęzłay,toelementwwęźlexjestniewiększyniżelementwwęźley. Jeśli spełniony jest warunek kopca mówimy, że drzewo ma uporządkowanie kopcowe, a jego elementy zachowują porządek kopcowy. Uporządkowanie kopcowe zapewnia, iż w korzeniu drzewa znajduje się największy element (lub jeden z największych, gdy jest ich kilka), zaś na ścieżkach w drzewie, od korzenia do liścia, elementy są uporządkowane w porządku nierosnącym. KOPIEC Wykład 6. Strona 15.
Kopiec zupełny Kopiec zupełny to kopiec i zarazem zupełne drzewo binarne, czyli takie, w którym wszystkie poziomy są wypełnione całkowicie z wyjątkiem co najwyżej ostatniego, który jest spójnie wypełniony od strony lewej. Ze względu na regularną strukturę kopca zupełnego, można go reprezentować w prosty sposób w tablicy. Następniki węzła k (o ile istnieją) mają odpowiednio numery 2k i 2k+1, zaś poprzednik węzła k (różny od korzenia) ma numer k/2 : KOPIEC ZUPEŁNY Wykład 6. Strona 16.
Operacje na kopcu zupełnym Wstawianie elementu (operacja Wstaw(x, S)) do kopca zupełnego: Usuwanie elementu maksymalnego (operacja UsuńMax(S)) z kopca zupełnego: Wykład 6. Strona 17.
Operacje na kopcu procedure insert (v : integer); // Pesymistyczna złożoność czasowa log n + 1. n :=n+1; a[n] := v; upheap (n) procedura upheap (k : integer); var 1, v : integer; v :=a[k]; a[0] := + ; // dużą wartość l:= k div 2; // warunek kopca jest zaburzony // co najwyżej tylko dla v} while a[l] < v do {węzeł l jest poprzednikiem węzła k} a[k] := a[l]; k:=l; l :=l div 2 a[k] := v function deletemax : integer; // Pesymistyczna złożoność czasowa 2 log n. deletemax := a[1]; a[1] := a[n]; n := n 1; downheap(1) procedure downheap (k : integer); var i, j, v : integer; v := a[k]; while k n div 2 do j := 2 * k; {j jest następnikiem k} if j < n then if a[j] < a[j + 1] then j := j + 1; if v a[j] then a[k] := v; k = n // celem zakończenia pętli end else a[k] := a[j]; k := j Wykład 6. Strona 18.
Operacje na kopcu i sortowanie kopca procedure construct; // Pesymistyczna złożoność czasowa O(n). // Elementy listy q=[a 1,...,a n ] // znajdują się w tablicy a[1..n] var i : integer; for i := n div 2 downto 1 do downheap(i) procedure heapsort; // Pesymistyczna złożoność czasowa wynosi // 2 n log n + O(n) = O(n log n). // Tablica a[1..n] zawiera elementy // do posortowania var m, i : integer; m := n; construct; for i := m downto 2 do a[i] := deletemax; n := m Algorytm sortowania kopcowego daje się zapisać za pomocą operacji kolejki priorytetowej następująco: Buduj(q, S); // O(n) for i:=1 to n-1 do UsuńMax(S); // (n-1)*o(log n) = O(n log n) dla danej listy q=[a 1,...,a n ] charakteryzuje się pesymistyczną złożonością czasową O(n log n). Wykład 6. Strona 19.
Kolejki priorytetowe Kolejki priorytetowe struktura danych, która realizuje następujące operacje: Buduj(q, S) - utworzenie multizbioru S={a 1,...,a n } dla danej listy q=[a 1,...,a n ], Wstaw(x, S) - S := S {x}, UsuńMax(S) - usunięcie największego elementu z S. W porównaniu do zwykłej kolejki (FIFO) z kolejki priorytetowej usuwany jest zawsze maksymalny element (czyli element o najwyższym priorytecie). Wykład 6. Strona 20.
Literatura i bibliografia: L. Banachowski, K. Diks, W. Rytter: Algorytmy i struktury danych, WNT, Warszawa, 2001 Z. Fortuna, B. Macukow, J. Wąsowski, Metody numeryczne, WNT, Warszawa, 1993. K. Jakubczyk, Turbo Pascal i Borland C++, Wydanie II, Helion, 2006. J. i M. Jankowscy, Przegląd metod i algorytmów numerycznych, WNT, Warszawa, 1988. A. Kiełbasiński, H. Schwetlick, Numeryczna algebra liniowa, WNT, Warszawa 1992. A. Kierzkowski, Turbo Pascal. Ćwiczenia praktyczne., Helion 2006. K. Koleśnik, Wstęp do programowania z przykładami w Turbo Pascalu, Helion, M. Sysło: Elementy Informatyki. A. Szepietowski: Podstawy Informatyki. R. Tadeusiewicz, P. Moszner, A. Szydełko: Teoretyczne podstawy informatyki. W. M. Turski: Propedeutyka informatyki. N. Wirth: Wstęp do programowania systematycznego. N. Wirth: ALGORYTMY + STRUKTURY DANYCH = PROGRAMY. Wikipedia Algorytmy sortujące: http://edu.i-lo.tarnow.pl/inf/alg/003_sort/index.php Algorytmy sortujące: http://www.home.umk.pl/~abak/wdimat/s/index.html Wykład 6. Strona 21.