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.

Podobne dokumenty