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