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;
technikę
rekurencji
do
odnalezienia wszystkich wartości nil
1 function rb_delete(var r:ptree; z:ptree; const g:ptree):ptree;
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
3 x,y:ptree;
4 begin
5 if z=nil then
6 begin
7
rb_delete:=nil;
8
exit;
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
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:
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;
13 x^.parent:=y^.parent;
14 if y^.parent=g then r:=x
Funkcja ta jest odpowiednikiem procedury
delete_node, która została zaprezentowana
15 else
16
if y=y^.parent^.left_child then y^.parent^.left_child:=x
17
else
na wcześniejszych wykładach. Zwraca ona
adres elementu, który trzeba usunąć. Jeśli
chcemy usunąć element, który nie ma lub
18
y^.parent^.right_child:=x;
ma tylko jednego potomka, to funkcja
19 if y<>z then z^.key:=y^.key;
zwróci właśnie jego adres. Jeśli węzeł,
który chcemy usunąć ma dwóch potom20 if y^.color=black then rb_delete_fixup(r,x);
ków, to zostanie zwrócony adres innego
21 rb_delete:=y;
elementu, który po zamianie wartości
może być bezpiecznie usunięty. Do wyszu22 end;
kania tego elementu jest stosowana funkcja pomocnicza tree_delete_successor, której kod zostanie zaprezentowany później. Część funkcji poświęcona wyznaczeniu
1
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
elementu do usunięcia nie będzie
omawiana szczegółowo, ze względu na
analogię do wspomnianej
wyżej
2
procedury
delete_node .
Po
1 function tree_successor(r:ptree; const g:ptree):ptree;
2 var
3 tmp:ptree;
wyznaczeniu węzła do usunięcia
funkcja rb_delete wywołuje procedurę
4 begin
rb_delete_fixup, jeśli usuwany węzeł
5 if r^.right_child <> g then
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
anomalii jest zadaniem procedury
rb_delete_fixup.
6 begin
7
tree_successor:=tree_min(r^.right_child,g);
8
exit;
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;
15 end;
16 tree_successor:=tmp;
17 end;
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
3
wartości, ze zbioru węzłów o wartościach większych od węzła, którego adres przekazaliśmy przez parametr r. 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 prze1 procedure rb_delete_fixup(var r,x:ptree);
kazywany
adres
jedynego
potomka
4
usuwanego czarnego węzła . 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 instrukwarunkowej w wierszu szóstym. Pierwsze
cztery przypadki zachodzą, kiedy węzeł
wskazywany przez x jest lewym potomkiem
2 var
3 w:ptree;
4 begin
cji
5 while (x<>r) and (x^.color=black) do
swojego przodka. Kolejne cztery zachodzą, kiedy ten węzeł jest prawym potomkiem swojego przodka i można ich rozwiązanie
2
3
Ta procedura była opisywana na wykładzie ósmym.
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
można otrzymać zmieniając nazwy pól left_child
6 if x=x^.parent^.left_child then
na right_child i odwrotnie w rozwiązaniu
7
begin
pierwszych czterech przypadków. W pętli while
8
w:=x^.parent^.right_child;
9
if w^.color = red then
10
begin
jednostki „wielokrotnie czarne” są przesuwane
w górę drzewa. Na taką jednostkę zawsze
wskazuje wskaźnik x. Pętla jest kończona jeśli
zajdzie któryś z trzech warunków: x
11
w^.color:=black;
12
x^.parent^.color:=red;
13
left_rotate(r,x^.parent);
14
w:=x^.parent^.right_child;
15
end;
16
if (w^.left_child^.color=black) and (w^.right_child^.color=black) then
17
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,
begin
23
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
zakończona, bo x będzie wskazywał na czerwony
węzeł. Po wyjściu z tej pętli jego kolor zostanie
zmieniony na czarno. Przypadek trzeci zachodzi,
kiedy węzeł wskazywany przez w (brat x)
end;
posiada lewego potomka, który jest czerwony i
prawego,
który
jest
czarny.
Wówczas
zamieniane są kolory w i jego lewego syna
end
37 else
38
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
begin
39
w:=x^.parent^.left_child;
40
if w^.color = red then
41
begin
42
można
pominąć
nadmiarowość
kolorów
czarnych, lub jeśli w wyniku rotacji węzłów i
zmian ich kolorów uda się wyrugować zbędny
czarny kolor. 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
18
będzie
wskazywał na węzeł czerwony – wówczas
możemy zmienić jego kolor na czarny (wiersz
68), lub x będzie wskazywał na korzeń – wtedy
wskazywanego
przez
x,
który
jest
czarny
i posiada prawego potomka, który jest czerwony.
To doprowadza do przypadku czwartego.
w^.color:=black;
3
Podstawy Programowania – semestr drugi
43
W
x^.parent^.color:=red;
44
right_rotate(r,x^.parent);
45
w:=x^.parent^.left_child;
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
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
wykonanie pętli while. Warto zaznaczyć, że
48
begin
49
w^.color:=red;
50
x:=x^.parent;
51
end
52
else
53
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
67
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”, ... , „z” oznaczają poddrzewa, duże
litery „A”, „B”, ... , „Z” 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.
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
E
d e
f
a
b
c
B
„x”
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