Temat: Drzewa binarne 1 Wstęp teoretyczny
Transkrypt
Temat: Drzewa binarne 1 Wstęp teoretyczny
Instrukcja Podstawy programowania 2 laboratoryjna 5 Temat: Drzewa binarne Przygotował: mgr inż. Tomasz Michno 1 Wstęp teoretyczny Drzewa są jedną z częściej wykorzystywanych struktur danych. Reprezentują drzewa matematyczne, które są grafami skierowanymi o ustalonej strukturze (m. in. do każdego węzła może dochodzić tylko jedno połączenie (krawędź), natomiast wychodzić może ich już wiele). Każde drzewo posiada element początkowy, nazywany korzeniem (ang. root), z którego wychodzą krawędzie do dalszych węzłów (nazywanych dziećmi). Węzeł, z którego nie wychodzą żadne krawędzie nazywany jest liściem (ang. leaf). Długość to liczba krawędzi, przez które należy przejść od korzenia do węzła i określa ona poziom węzła. Wysokością drzewa nazywamy natomiast maksymalny poziom w drzewie. Drzew używa się zazwyczaj do reprezentacji danych o ustalonej hierarchii. Drzewo binarne jest drzewem, w którym każdy węzeł może mieć maksymalnie 2 dzieci. Przykład: korzeń 3 2 1 Poziom: 1 6 5 liście Poziom: 0 4 Poziom: 2 Wysokość drzewa: 2 Binarne drzewo poszukiwań (ang. Binary Search Tree, BST) jest drzewem binarnym, które dodatkowo spełnia następujące właściwości: • każde dziecko znajdujące się na lewo (lewe poddrzewo) zawiera elementy o wartościach nie większych niż wartość węzła • każde dziecko znajdujące się na prawo (prawo poddrzewo) zawiera elementy o wartościach nie mniejszych niż wartość węzła Przykład: 4 2 1 5 3 6 Proponowana struktura węzła drzewa: type WSKWezel = ^ Wezel; Wezel = record dane : integer; lewy : WSKWezel; prawy : WSKWezel; end; Wyszukiwanie elementu w drzewie BST: wyszukaj(korzeń, dane): 1. if korzeń = nil then Nie znaleziono węzła. Koniec algorytmu. 2. if korzeń.dane = dane then Znaleziono węzeł. Koniec algorytmu. 3. if dane < korzeń.dane then wyszukaj(korzeń.lewy, dane) 4. if dane > korzeń.dane then wyszukaj(korzeń.prawy, dane) Wstawianie elementu do drzewa BST: 1. Należy znaleźć węzeł, do którego zostanie dołączony nowy element: ◦ sposób wyszukiwania jest bardzo zbliżony do wyszukiwania elementu: węzeł:=korzeń; Powtarzaj w pętli: if dane = węzeł^.dane then Węzeł już istnieje. Zakończ algorytm. if dane < węzeł^.dane then begin if węzeł^.lewy <> nil then węzeł := węzeł^.lewy else break; end else begin if węzeł^.prawy<> nil then węzeł := węzeł^.prawy else break; end; 2. Dodaj do znalezionego węzła nowy element – dziecko: if dane < węzeł^.dane then begin węzeł^.lewy:= Nowy węzeł(dane); end else begin węzeł^.prawy:=Nowy węzeł(dane); end; Przykład: dodanie do drzewa wartości 2.5: Wyszukiwanie rodzica: 2.5 < 4 4 2.5 > 2 2 5 2.5 < 3 1 3 6 4 Wstawienie nowego węzła 2 5 1 6 3 2.5 Zarówno w przykładzie, jak i algorytmie została pominięta sytuacja, w której drzewo jest puste (w takiej sytuacji dodawany element jest korzeniem). Usuwanie węzła Usuwanie węzła w drzewie BST jest skomplikowaną operacją. Mogą wystąpić 3 sytuacje: 1. Usuwany węzeł nie ma dzieci. 2. Usuwany węzeł ma tylko lewego lub tylko prawego potomka 3. Usuwany węzeł ma lewego i prawego potomka. Sytuacja 1: Usuwamy element 3: 4 2 1 Sytuacja 2: 4 2 5 3 6 1 5 6 Usuwamy element 5. Usuwany element jest zastępowany potomkiem. 4 4 2 1 5 2 6 3 6 1 3 Sytuacja 3: Usuwamy element 4. (Do drzewa zostały dodane węzły w celu lepszego zobrazowania wykonywanych działań). Krok 1. Znalezienie skrajnie prawego potomka w lewym poddrzewie (jest to węzeł o największej wartości mniejszej od usuwanego węzła; jego cechą jest brak prawego potomka). 4 2 Skrajnie prawy potomek: 3 1 5 3 2.5 4.5 4.1 Krok 2. Przepisanie danych ze skrajnie prawego potomka do usuwanego węzła: 3 2 1 5 3 2.5 4.5 4.1 6 6 Krok 3. Usunięcie skrajnie prawego potomka i zastąpienie go jego lewym potomkiem: 3 3 2 1 3 2.5 2 5 4.5 5 1 6 2.5 4.5 6 4.1 4.1 Przechodzenie przez drzewo: Operacja przechodzenia przez drzewo pozwala na odwiedzenie wszystkich jego węzłów. Wyróżniamy następujące typy tej operacji: • pre-order – przejście wzdłużne • in-order – przejście poprzeczne • post-order – przejście wsteczne Pre-order: Na początku odwiedzany jest węzeł, a następnie jego lewy i prawy potomek. Przykład: 4 4, 2, 1, 3, 5, 6 2 5 1 3 6 In-order: Na początku odwiedzany jest lewy potomek węzła, potem sam węzeł, a następnie jego prawy potomek. 4 Przykład: 2 1, 2, 3, 4, 5, 6 1 5 3 6 Post-order: Na początku odwiedzany jest lewy potomek węzła, potem prawy potomek, a na końcu sam węzeł. Przykład: 4 1, 3, 2, 6, 5, 4 2 1 5 3 6 Jak można było zauważyć przechodzenie in-order dało nam uporządkowaną listę liczb znajdujących się w drzewie. Operacje przechodzenia przez drzewo najłatwiej jest zrealizować z wykorzystaniem rekurencji. Należy jednak pamiętać o ograniczeniach, jakie niesie ona ze sobą – dla bardzo dużych drzew może nastąpić przepełnienie sterty i zakończenie programu z błędem. 2 Zadania 1. Napisz program konstruujący drzewo BST, w którym będą przechowywane duże litery (10 dowolnie wybranych). Należy zaimplementować operację dodawania oraz wyszukiwania. Drzewo powinno zostać również wyświetlone, np. z użyciem poniższego kodu (procedury wyswietlDrzewo): type Array50x50 = Array[0..50, 0..50] of char; {...} function znajdzWysokoscDrzewa(wezel : WSKWezel):integer; var lewy, prawy : integer; begin if(wezel=nil) then begin znajdzWysokoscDrzewa:=-1; exit; end; lewy:=znajdzWysokoscDrzewa(wezel^.lewy); prawy:=znajdzWysokoscDrzewa(wezel^.prawy); if(lewy < prawy ) then znajdzWysokoscDrzewa:=prawy+1 else znajdzWysokoscDrzewa:=lewy+1; end; function znajdzDlugosc(korzen : WSKWezel; dane : char) : integer; var dlugosc : integer; begin dlugosc:=-1; while(korzen<>nil) do begin dlugosc:=dlugosc+1; if(korzen^.dane = dane) then break; if(dane < korzen^.dane) then korzen:=korzen^.lewy else korzen:=korzen^.prawy; end; znajdzDlugosc:=dlugosc; end; procedure wypelnijTabliceDrzewem(wezel : WSKWezel; var tab: Array50x50; var ktoryOdLewej : integer); var dlugosc: integer; begin if(wezel=nil) then exit; if(ktoryOdLewej>50) then exit; wypelnijTabliceDrzewem(wezel^.lewy, tab, ktoryOdLewej); dlugosc:=znajdzDlugosc(drzewo, wezel^.dane); if(dlugosc>50) then exit; ktoryOdLewej:=ktoryOdLewej+1; tab[dlugosc][ktoryOdLewej]:=wezel^.dane; wypelnijTabliceDrzewem(wezel^.prawy, tab, ktoryOdLewej); end; procedure wyswietlDrzewo(wezel : WSKWezel); var tablica : Array50x50; {tablica wykorzystywana przy wyswietlaniu} ktoryOdLewej : integer; i,j : integer; begin ktoryOdLewej:=0; for j:=0 to 50 do for i:=0 to 50 do tablica[j][i]:=' '; wypelnijTabliceDrzewem(wezel, tablica, ktoryOdLewej); writeln('Max na prawo: ', ktoryOdLewej); readln; for j:=0 to znajdzWysokoscDrzewa(wezel) do begin for i:=0 to ktoryOdLewej do write(tablica[j][i],' '); writeln; end; end; 2. Do programu z zadania 1 dodaj operacje przechodzenia przez drzewo (pre-order, in-order, post-order) oraz usuwanie całego drzewa (zwalnianie całej pamięci wykorzystywanej przez drzewo). 3. Napisz program, który będzie sortował ciągi liczb (dowolnej długości) podawane przez użytkownika z wykorzystaniem drzewa BST. 4. Do programu z zadania 3 dodaj procedury/funkcje obliczające: ◦ średnią wszystkich liczb w drzewie ◦ sumę wszystkich liczb w drzewie 5. Do programu z zadania 4 dodaj procedurę usuwającą z drzewa liczbę podaną przez użytkownika. Wyświetl drzewo przed i po usunięciu.