Instrukcja laboratoryjna 5 Podstawy programowania 2 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. korzeń Poziom: 0 3 2 6 Poziom: 1 1 5 Poziom: 2 liście 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
Proponowana struktura węzła drzewa: type WSKWezel = ^ Wezel; Wezel = record dane : integer; lewy : WSKWezel; prawy : WSKWezel; 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). 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 if węzeł^.lewy <> nil then węzeł := węzeł^.lewy else break; end else if węzeł^.prawy<> nil then węzeł := węzeł^.prawy else break; 2. Dodaj do znalezionego węzła nowy element dziecko: if dane < węzeł^.dane then węzeł^.lewy:= Nowy węzeł(dane); end else węzeł^.prawy:=nowy węzeł(dane); dodanie do drzewa wartości 2.5: Wyszukiwanie rodzica: 2.5 < 2.5 > 2 2.5 < 3
Wstawienie nowego węzła 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: 1 6 Sytuacja 2:
Usuwamy element 5. Usuwany element jest zastępowany potomkiem. 2 6 1 3 Sytuacja 3: Usuwamy element. (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). Skrajnie prawy potomek: 3 1 3.5 6 2.5.1 Krok 2. Przepisanie danych ze skrajnie prawego potomka do usuwanego węzła: 3 1 3.5 6 2.5.1
Krok 3. Usunięcie skrajnie prawego potomka i zastąpienie go jego lewym potomkiem: 3 3 1 3.5 6 1 2.5.5 6 2.5.1.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., 2, 1, 3, 5, 6 In-order: Na początku odwiedzany jest lewy potomek węzła, potem sam węzeł, a następnie jego prawy potomek. 1, 2, 3,, 5, 6
Post-order: Na początku odwiedzany jest lewy potomek węzła, potem prawy potomek, a na końcu sam węzeł. 1, 3, 2, 6, 5, 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; if(wezel=nil) then znajdzwysokoscdrzewa:=-1; exit;
lewy:=znajdzwysokoscdrzewa(wezel^.lewy); prawy:=znajdzwysokoscdrzewa(wezel^.prawy); if(lewy < prawy ) then znajdzwysokoscdrzewa:=prawy+1 else znajdzwysokoscdrzewa:=lewy+1; function znajdzdlugosc(korzen : WSKWezel; dane : char) : integer; var dlugosc : integer; dlugosc:=-1; while(korzen<>nil) do dlugosc:=dlugosc+1; if(korzen^.dane = dane) then break; if(dane < korzen^.dane) then korzen:=korzen^.lewy else korzen:=korzen^.prawy; znajdzdlugosc:=dlugosc; procedure wypelnijtablicedrzewem(wezel : WSKWezel; var tab: Array50x50; var ktoryodlewej : integer); var dlugosc: integer; 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); procedure wyswietldrzewo(wezel : WSKWezel); var
tablica : Array50x50; {tablica wykorzystywana przy wyswietlaniu} ktoryodlewej : integer; i,j : integer; 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 for i:=0 to ktoryodlewej do write(tablica[j][i],' '); writeln; 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.. 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 dodaj procedurę usuwającą z drzewa liczbę podaną przez użytkownika. Wyświetl drzewo przed i po usunięciu.