to get the file

Transkrypt

to get the file
Podstawy Programowania – semestr drugi
Wykład dziewiąty
1. Usuwanie elementów z drzewa czerwono-czarnego
Operacja usuwania węzła z drzewa czerwono – czarnego jest pojęciowo bardziej skomplikowana niż operacja wstawiania, ale
jej złożoność obliczeniowa jest taka sama, wynosi O(lg n). Aby uprościć obsługę przypadków, kiedy podczas usuwania
będziemy dokonywać pewnych działań na wartościach nil, możemy pozbyć się tych wartości wstawiając w ich miejsce odwołanie do (adres) węzła wartownika1. Węzeł ten ma kolor czarny (co wynika z jednego z założeń drzewa czerwono – czarnego).
Wartości pozostałych pól są na ogół nieistotne, za wyjątkiem sytuacji, kiedy usuwamy przodka takiego węzła. Zanim to uczynimy, musimy przypisać węzłowi wartownika wskazanie na swojego przodka. Można utworzyć kilkanaście węzłów – wartowników, ale nie ma to żadnego uzasadnienia, a więc celem zaoszczędzenia pamięci tworzy się jeden taki węzeł. Procedura,
która wstawia węzeł wartownika w miejsce wartości nil, które są traktowane jako liście drzewa jest następująca:
1 procedure insert_guard(var r:ptree; const g:ptree);
2 begin
3
if r^.left_child=nil then r^.left_child:=g else insert_guard(r^.left_child,g);
4
if r^.right_child=nil then r^.right_child:=g else insert_guard(r^.right_child,g);
5 end;
1 function rb_delete(var r:ptree; z:ptree; const g:ptree):ptree;
3 x,y:ptree;
4 begin
5 if z=nil then
6 begin
rb_delete:=nil;
8
exit;
technikę
rekurencji
do
nalezienia
wszystkich
wartości
odnil
pełniących rolę liści w drzewie czerwonoczarnym i do zamiany ich na adresy wartownika. Nie uwzględnia on wartości „nil”
w polu „parent” korzenia, a więc
w programie należy umieścić następującą
instrukcję, aby pozbyć się tej wartość:
root^.parnet:=guard.
2 var
7
Procedura ta pobiera jako drugi
parametr wywołania adres węzła
wartownika. Ten węzeł należy
oczywiście stworzyć wcześniej, tak
aby
odpowiadał
on
opisowi
umieszczonemu
wyżej.
Podprogram
insert_guard
stosuje
9 end;
10 if (z^.left_child=g) or (z^.right_child=g) then y:=z
11 else y:=tree_successor(z,g);
12 if y^.left_child<>g then x:=y^.left_child else x:=y^.right_child;
Mając drzewo z wartownikiem możemy
przeprowadzić operację usuwania dowolnego elementu należącego do tego drzewa.
Operacja ta przebiega podobnie jak operacja usuwania węzła z drzewa BST. Najpierw musimy wyznaczyć element, który
faktycznie ma zostać usunięty. Możemy to
zrealizować przy pomocy następującej
funkcji:
13 x^.parent:=y^.parent;
Funkcja ta jest odpowiednikiem procedury delete_node, która została zaprezen -
14 if y^.parent=g then r:=x
15 else
16
if y=y^.parent^.left_child then y^.parent^.left_child:=x
17
else
18
y^.parent^.right_child:=x;
19 if y<>z then z^.key:=y^.key;
20 if y^.color=black then rb_delete_fixup(r,x);
21 rb_delete:=y;
22 end;
1
towana na wcześniejszych wykładach.
Zwraca ona adres elementu, który trzeba
usunąć. Jeśli chcemy usunąć element,
który nie ma potoków lub ma tylko
jednego potomka, to funkcja zwróci
właśnie jego adres. Jeśli węzeł, który
chcemy usunąć ma dwóch potomków, to
zostanie zwrócony adres innego elementu,
który po zamianie wartości może być
bezpiecznie usunięty. Do wyszukania tego
elementu
jest
stosowana
funkcja
Uwzględnianie w kodzie podprogramów realizujących jakieś operacje na strukturach danych sytuacji, w których wykonywane są pewne czynności na wskaźnikach, które mogą mieć wartość „nil” nazywa się rozpatrywaniem warunków brzegowych.
1
Podstawy Programowania – semestr drugi
pomocnicza
1 function tree_successor(r:ptree; const g:ptree):ptree;
2 var
3 tmp:ptree;
4 begin
5 if r^.right_child <> g then
wyznaczeniu węzła do usunięcia
funkcja rb_delete wywołuje procedurę
6 begin
7
tree_successor:=tree_min(r^.right_child,g);
8
exit;
rb_delete_fixup, jeśli usuwany węzeł
był węzłem czarnym. W takim
przypadku może bowiem dojść do
naruszenia
czwartego
założenia
odnośnie budowy drzewa czerwono –
czarnego, dotyczącego ilości czarnych
węzłów na ścieżce od dowolnego węzła
tego drzewa do liści. Ten problem
możemy usunąć nadając węzłowi, który
był potomkiem usuniętego elementu
kolor czarny. To niestety może
spowodować, że ten węzeł stanie się
„podwójnie czarny” (jeśli był już
czarny), co narusza z kolei założenie
odnośnie budowy drzewa czerwono –
czarnego mówiące, że dany węzeł może
być albo czerwony, albo czarny
(pierwsze założenie). Usunięcie tej
9 end;
10 tmp:=r^.parent;
11 while (tmp<>g) and (r=tmp^.right_child) do
12 begin
13
r:=tmp;
14
tmp:=tmp^.parent;
tree_delete_successor,
której kod został zaprezentowany obok.
Część funkcji poświęcona wyznaczeniu
elementu do usunięcia nie będzie
omawiana szczegółowo, ze względu na
analogię
do
wspomnianej
wyżej
procedury
delete_node 2.
Po
15 end;
16 tree_successor:=tmp;
17 end;
anomalii jest zadaniem procedury rb_delete_fixup.
Powyżej znajduje się kod pomocniczej funkcji tree_successor , wywoływanej przez rb_delete celem ustalenia węzła do usunięcia w przypadku, kiedy węzeł, który powinien być usunięty ma dwóch potomków. Wyszukuje ona węzeł o najmniejszej
wartości, ze zbioru węzłów o wartościach większych od węzła, którego adres przekazaliśmy przez parametr r.3 Funkcja
tree_minimum ma następującą postać:
1 function tree_min(r:ptree; const g:ptree):ptree;
2 begin
3 while r^.left_child<>g do r:=r^.left_child;
4 tree_min:=r;
5 end;
Kod procedury „rb_delete_fixup” jest następujący:
Przez parametr x tej procedurze jest przekazy1 procedure rb_delete_fixup(var r,x:ptree);
wany adres jedynego potomka usuwanego
czarnego węzła4. W tej procedurze jest ujętych
osiem przypadków, ale cztery z nich są symetryczne w stosunku do czterech pozostałych.
Rozróżnienie zachodzi w instrukcji warunkowej w wierszu szóstym. Pierwsze cztery
przypadki zachodzą, kiedy węzeł wskazywany
2 var
3 w:ptree;
4 begin
5 while (x<>r) and (x^.color=black) do
2
3
Ta procedura była opisywana na wykładzie siódmy.
Jeśli ta funkcja jest wywoływana wewnątrz rb_delete, to przypadek kiedy element usuwany nie ma prawego potomka nie jest
nigdy spełniony, a więc nigdy nie jest wykonywana pętla while.
4
Dzięki wartownikowi zawsze taki potomek istnieje.
2
Podstawy Programowania – semestr drugi
przez x jest lewym potomkiem swojego przodka.
6 if x=x^.parent^.left_child then
7
Kolejne cztery zachodzą, kiedy ten węzeł jest
prawym potomkiem swojego przodka i można
ich rozwiązanie można otrzymać zmieniając
nazwy pól left_child na right_child i odwrotnie,
begin
8
w:=x^.parent^.right_child;
9
if w^.color = red then
10
begin
w rozwiązaniu pierwszych czterech przypadków.
W pętli while jednostki „wielokrotnie czarne” są
11
w^.color:=black;
przesuwane w górę drzewa. Na taką jednostkę
zawsze wskazuje wskaźnik x. Pętla jest
12
x^.parent^.color:=red;
kończona jeśli zajdzie któryś
warunków: x będzie wskazywał
13
left_rotate(r,x^.parent);
14
w:=x^.parent^.right_child;
czerwony – wówczas możemy zmienić jego kolor
na czarny (wiersz 68), lub x będzie wskazywał
15
end;
16
if (w^.left_child^.color=black) and (w^.right_child^.color=black) then
17
begin
18
w^.color:=red;
19
x:=x^.parent;
20
end
21
else
22
if w^.right_child^.color=black then
24
begin
25
w^.left_child^.color:=black;
26
w^.color:=red;
27
right_rotate(r,w);
28
w:=x^.parent^.right_child;
29
end;
30
w^.color:=x^.parent^.color;
31
x^.parent^.color:=black;
32
w^.right_child^.color:=black;
33
left_rotate(r,x^.parent);
34
x:=r;
35
36
Jeśli jest on czerwony, to zachodzi pierwszy
przypadek. Wówczas jego kolor zmieniany jest
na czarny, a kolor jego rodzica zmieniany na
czerwony i wykonywana jest rotacja w lewo.
Operacja ta nie zmienia ilości czarnych węzłów
na ścieżce od korzenia do dowolnego poddrzewa
rozważanych węzłów. Po wykonaniu tej
czynności wyznaczany jest „nowy brat” węzła x,
który jest czarny. Teraz mogą wystąpić już tylko
przypadki drugi, trzeci i czwarty, które są
rozróżniane zależnie do kolorów potomków
węzła wskazywanego przez w. W drugim
przypadku obaj potomkowie tego węzła są
czarni. Usuwamy więc kolor czarny z węzłów
wskazywanych przez w i x, które teraz stają się
odpowiednio węzłem czerwonym i czarnym,
a usunięty kolor przenosimy na ich przodka
(wiersze 16 – 20). Ponieważ nic nie wiemy na
temat jego koloru, więc należy pętlę while
powtórzyć, ale z x wskazującym na niego. Jeśli
ten przypadek zaistniał w wyniku rozwiązania
przypadku pierwszego, to pętla while zostanie
end;
zakończona, bo x będzie wskazywał na czerwony
węzeł. Po wyjściu z tej pętli jego kolor zostanie
zmieniony na czarny. Przypadek trzeci zachodzi,
kiedy węzeł wskazywany przez w (brat x)
end
37 else
38
posiada lewego potomka, który jest czerwony
i prawego,
który
jest
czarny.
Wówczas
zamieniane są kolory w i jego lewego syna
begin
39
w:=x^.parent^.left_child;
40
if w^.color = red then
41
begin
42
na korzeń – wtedy można pominąć nadmiarowy
kolor czarny, lub za pomocą zmian kolorów
i rotacji uda się pozbyć nadmiarowego koloru
czarnego.
Teraz
omówimy
rozwiązania
wspomnianych
czterech
pierwszych
przypadków. W wierszy ósmym w zmiennej
w zapamiętywany jest adres prawego potomka
węzła,
który
jest
przodkiem
węzła
wskazywanego przez x (czyli na „brata” węzła x).
begin
23
z trzech
na węzeł
i wykonywana jest rotacja w prawo, co nie
powoduje naruszenia żadnej z własności drzewa
czerwono
–
czarnego.
W wierszu
28
zapamiętujemy w w adres nowego „brata” węzła
w^.color:=black;
wskazywanego
3
przez
x,
który
jest
czarny
Podstawy Programowania – semestr drugi
43
x^.parent^.color:=red;
44
right_rotate(r,x^.parent);
45
w:=x^.parent^.left_child;
i posiada prawego potomka, który jest
czerwony. To doprowadza do przypadku
czwartego. W tym przypadku „bratu” węzła
x przypisujemy kolor jego przodka, a przodkowi
i prawemu potomkowi węzła w kolor czarny.
46
end;
47
if (w^.right_child^.color=black) and (w^.left_child^.color=black) then
48
begin
49
w^.color:=red;
50
x:=x^.parent;
51
begin
54
if w^.left_child^.color=black then
55
begin
56
w^.right_child^.color:=black;
57
w^.color:=red;
58
left_rotate(r,w);
59
w:=x^.parent^.left_child;
60
end;
61
w^.color:=x^.parent^.color;
62
x^.parent^.color:=black;
63
w^.left_child^.color:=black;
64
right_rotate(r,x^.parent);
65
x:=r;
66
68
niezmiennikiem
w
rozwiązaniach
poszczególnych przypadków jest zachowanie
ilości węzłów czarnych w ścieżkach, do których
należą rozważane węzły. Poniżej przedstawiono
ilustrację działania tej procedury dla każdego
z czterech opisanych przypadków. Małe litery
„a”, „b”, ... , „f” oznaczają poddrzewa, duże
litery „A”, „B”, ... , „E” oznaczają poszczególne
węzły drzewa. Jeśli węzeł nie jest oznaczony
ani kolorem czerwonym, ani czarnym, to
znaczy, że jego kolor nie ma w danym
przypadku większego znaczenia.
else
53
67
wykonanie pętli while. Warto zaznaczyć, że
end
52
Następnie wykonujemy rotację w lewo, co
powoduje usunięcie nadmiarowego czarnego
koloru. W wierszu 34 zapamiętujemy adres
korzenia drzewa w zmiennej x, co kończy
end;
end;
x^.color:=black;
69 end;
2. Podsumowanie
Pomimo, że niektóre operacje przeprowadzane na drzewach czerwono – czarnych są dosyć skomplikowane w zapisie ich czas
działania jest, zgodnie z założeniami równy O(lg n). Głównym źródłem informacji na temat drzew czerwono – czarnych jest
książka „Wprowadzenie do algorytmów” autorstwa Thomasa H.Cormena, Charlsa E.Leisersona i Ronalda L. Rivesta. O tych
strukturach wspomina także D.E.Knuth w trzeciej części „Sztuki programowania”, nazywając je symetrycznymi B – drzewami binarnymi lub drzewami półzrównoważonymi.
4
Podstawy Programowania – semestr drugi
B
D
„x”
A
„w”
D
Przypadek 1
E
B
„x”
a
C
b
„w”
A
E
C
a
c
d
e
b
c
d
B
„x”
A
f
f
B
„x”
e
„w”
D
D
A
Przypadek 2
C
C
a
b
c
d e
f
a
b
c
B
„x”
E
E
d
e
f
B
D
A
„w”
Przypadek 3
„x”
A
C
„w”
D
a
E
C
b
c
E
d
a
b
c
d
e
f
e
f
B
D
„x”
A
D
„w”
Przypadek 4
a
b
E
C
c
E
B
A
d
e
f
a
5
C
b
c
d
e
f