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