Wstęp Statyczne struktury danych Dynamiczne struktury danych Algorytmy i struktury danych Struktury danych IS/IO, WIMiIP Danuta Szeliga AGH Kraków Wstęp Statyczne struktury danych Dynamiczne struktury danych Spis treści I 1 Wstęp Pojęcia podstawowe Abstrakcyjne typ danych Statyczna/dynamiczna struktura danych 2 Statyczne struktury danych Tablica Rekord 3 Dynamiczne struktury danych Lista Stos Kolejka Drzewa Drzewa Drzewa Drzewa Drzewa binarne poszukiwań binarnych - BST AVL czerwono-czarne Wstęp Statyczne struktury danych Dynamiczne struktury danych Pojęcia podstawowe Dane w komputerze przechowywane są w postaci binarnej — dla komputera to jednolita masa bitów Człowiekowi wygodnie jest używać abstrakcyjnego modelu części świata rzeczywistego Potrzebna jest zatem metodologia ustrukturalizowania i precyzyjnego zdefiniowania informacji, które następnie mogą być przechowywane i przetwarzane → "nałożenie" abstrakcyjnej struktury Wstęp Statyczne struktury danych Dynamiczne struktury danych Abstrakcyjne typy danych Abstrakcyjne Typy Danych (ATD) to modele matematyczne uogólniajace pewna kategorie obiektów, wykazujacych określone zachowanie i posiadajacych określoną strukturę Zbiór operacji podstawowych, które mozna wykonać na określonym ATD musi zawierać co najmniej jeden element Argumenty i wyniki operacji na ATD mogą być danymi tego ATD lub innych Abstrakcyjne typy danych mogą być wzajemnie w sobie zagnieżdżane Implementacja ATD polega na zdefiniowaniu jego odpowiednika w konkretnym jezyku programowania Przykłady ATD Liczby rzeczywiste: zdefiniowane sa operacje dodawania, odejmowania, mnożenia itd. Liczby zespolone: zagnieżdżenie dwóch egzemplarzy danych innego ATD; zdefiniowane są operacje dodawania, odejmowania itd. Kolejka: zdefiniowane są operacje: dodaj, pobierz, czyPusta Wstęp Statyczne struktury danych Dynamiczne struktury danych Typ danych Typ danej definiuje Zbiór możliwych wartości, które może przyjmować obiekt Sposób kodowania informacji i przechowywania w pamięci Możliwe operacje, które mogą być na obiekcie wykonywane Typ danej/obiektu opisuje pewną podklasę informacji, które mogą być wyrażane i przechowywane za pomocą tej danej Typ możemy traktować abstrakcyjnie i wtedy z reguły jest określany przez operacje działające na obiekcie danego typu W językach programowania możemy używać ściśle określonych typów Typów, które dostarcza nam kompilator → typy wbudowane (podstawowe i złożone) Typów, które możemy sami konstruować, używając tych, które są już zdefiniowane → strukturalizacja informacji i tworzenie hierarchii typów Wstęp Statyczne struktury danych Dynamiczne struktury danych Pojęcia podstawowe Założenie Każdy obiekt (stała lub zmienna), wyrażenie i funkcja jest pewnego typu Stała to obiekt który nie zmienia swojej wartości Zmienna to obiekt, który może zmieniać swoją wartość Struktura danych to szczegółowe rozwiązanie implementacyjne sposobu przechowywania danych pewnego typu — zbiór obiektów określonych typów, posiadający swoistą organizację i związany z nią sposób wykorzystania Wstęp Statyczne struktury danych Dynamiczne struktury danych Pojęcia podstawowe Struktura danych jest spójna jeżeli dla każdych dwóch różnych jej obiektów A i B istnieje ciąg obiektów rozpoczynający się w A i kończących w B, a dla każdych dwóch kolejnych obiektów w ciągu pierwszy z nich jest następnikiem drugiego lub drugi jest następnikiem pierwszego Struktura danych jest liniowa gdy ma jedną funkcję określającą następnika tak, że w strukturze występuje dokładnie jeden obiekt początkowy i dokładnie jeden końcowy (beznastępnikowy), bądź też wszystkie obiekty są początkowe Struktura danych jest drzewiasta gdy posiada dokładnie jeden obiekt początkowy, a dla każdego obiektu poza początkowym istnieje w strukturze dokładnie jeden poprzednik Grafową strukturą danych jest dowolna struktura danych Wstęp Statyczne struktury danych Dynamiczne struktury danych Pojęcia podstawowe Statyczna struktura danych Dynamiczna struktura danych nie zmienia swojego rozmiaru ani struktury w trakcie działania algorytmu może zmieniać swój rozmiar i strukturę w trakcie działania algorytmu Większość języków programowania ma wbudowane mechanizmy wspierające tworzenie statycznych typów danych Najczęściej są to tablice rekordy (struktury) pliki (ciągi) Dynamiczne struktury udostęniane są najczęściej w bibliotekach lub wymagają implementacji Najczęściej są to listy drzewa grafy Wstęp Statyczne struktury danych Dynamiczne struktury danych Przykłady struktur danych Liniowe struktury danych lista/wektor tablica (statyczna, dynamiczna, rzadka, macierz) lista z dowiązaniami (jedno- i dwukierunkowa) stos kolejka (jedno- i dwukierunkowa, priorytetowa) tablica asocjacyjna/słownik/mapa Nieliniowe struktury danych grafowe struktury danych macierz sąsiedztwa listy sąsiedztwa stos o strukturze grafowej baza danych drzewiaste struktury danych B-drzewa drzewa binarne (BST, AVL, Red-black) kopce Wstęp Statyczne struktury danych Dynamiczne struktury danych Tablica Tablica to struktura danych jednorodna, składa się z obiektów tego samego typu o dostępie swobodnym, wszystkie składowe mogą być wybrane w dowolnej kolejności i są jednakowo dostępne składowe są dostępne przez indeksowanie W większości języków programowania tablica zajmuje ciągły obszar pamięci, a mechanizm obsługi tablic jest wbudowany w język C/C++ C# Java float x [10]; float y [5][5]; z = x [2]+ y [2][3]; float [] x = new float [10]; float [ ,] y = new float [5 ,5]; z = x [2]+ y [4 ,3]; float [] x = new float [10]; float [][] y = new float [5][5]; z = x [2]+ y [2][3]; Wstęp Statyczne struktury danych Dynamiczne struktury danych Rekord/struktura Rekord to struktura danych niejednorodna, grupuje kilka powiązanych logicznie ze sobą danych, które mogą być różnych typów o dostępie swobodnym dane stanowią pola rekordu W większości języków programowania rekord (jako całość) zajmuje ciągły obszar pamięci, choć ze względu na różne rozmiary danych składowych, czasami poszczególne składowe rekordu nie są składowane w sposób ciągły (wyrównywanie do granicy słowa) C/C++ C# struct Person { string name ; short age ; } Mike ; Mike . age = 10; struct Person { public string name ; public short age ; } Mike ; Mike . age = 10; Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista z dowiązaniami (Linked list) Lista to liniowa struktura danych, zbudowana z sekwencji węzłów (nodes), zawierających dane oraz co najmniej jeden odnośnik (link, referencję) do kolejnego węzła (→ lista jednokierunkowa,singly-linked list); węzeł może zawierać również odnośnik do węzła poprzedniego (→ lista dwukierunkowa) W porównaniu do tablicy, logiczna kolejność elementów listy może być inna od kolejności fizycznej (w pamięci) Lista nie zapewnia swobodnego dostępu do jej elementów (z wyjątkiem pierwszego (head)) lecz dostęp sekwencyjny Implementacja: tablicowa lub wskaźnikowa Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista Podstawowe operacje Sprawdzenie, czy lista jest pusta Wstawienie elementu na początek listy Wstawianie elementu wewnątrz listy Usuwanie elementu z listy Przeglądanie listy Wyszukiwanie elementu w liście Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista Implementacja tablicowa listy jednokierunkowej const int size = 100; struct NODE { T val ; // wartość int next ; // indeks następnego elementu listy } list [ size ]; // lista o max . rozmiarze = size Lista zaimplementowana w ten sposób opiera się na tablicy obiektów (lub rekordów) danego typu Można również zaimplementować taką listę na dwóch tablicach (jedna dla wartości, druga dla wskaźników) Niewykorzystane pola tablicy łączone są w postaci osobnej listy dla łatwiejszego ich wykorzystania + Jedyny sposób zaimplementowania listy w językach, które nie posiadają wskaźników − Ograniczona elastyczność (stały rozmiar tablicy) Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista (implementacja tablicowa) Wstawianie na początek listy bool insertHead ( NODE lista [] , int & first , int & free , T x ) { if ( first == -1) { // jeżeli lista jest pusta lista [ first = free ]. val = x ; free = lista [ free ]. next ; list [ first ]. next = -1; return true ; } if ( free == -1) return false ; // brak miejsca tmp = lista [ free ]. next ; lista [ free ]. val = x ; lista [ free ]. next = first ; first = free ; free = tmp ; return true ; // wstawiono } Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista Implementacja wskaźnikowa listy jednokierunkowej struct NODE { T val ; // wartość NODE * next ; // wskaźnik do następnego elementu listy } * head = null ; // początkowo lista jest pusta Lista zaimplementowana w ten sposób opiera się na kolekcji powiązanych ze sobą obiektów utworzonych dynamicznie + Duża elastyczność (ograniczona jedynie ilością dostępnej pamięci) − Kolejne elementy listy nie muszą znajdować się w kolejnych komórkach pamięci Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista (implementacja wskaźnikowa) Wstawianie na początek listy insertHead ( NODE * & head , T x ) { NODE * tmp = new NODE ; tmp - > val = x ; tmp - > next = head ; head = tmp ; } Uwagi: Wskaźnik do head będzie modyfikowany → przesyłany do funkcji przez referencję Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista (implementacja wskaźnikowa) Wstawianie elementu do listy Wstawianie PO elemencie insertAfter ( NODE * p , T x ) { NODE * tmp = new NODE ; tmp - > val = x ; tmp - > next =p - > next ; p - > next = tmp ; } Wstawianie PRZED elementem Ponieważ nie mamy wskaźnika na poprzedni element, nowy element wstawiamy PO p, a następnie podmieniamy wartości insertBefore ( NODE * p , T x ) { NODE * tmp = new NODE ; tmp - > val = p - > val ; tmp - > next = p - > next ; p - > val = x ; p - > next = tmp ; } Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista (implementacja wskaźnikowa) Usuwanie elementu z listy Usuwanie następnika p - nie dla ostatniego elementu: bool deleteAfter ( NODE * p ) { NODE * tmp = p - > next ; if ( tmp == null ) return false ; p - > next = tmp - > next ; delete tmp ; return true ; } Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista (implementacja wskaźnikowa) Usuwanie elementu z listy cd. Usuwanie p Ponieważ nie mamy wskaźnika na poprzedni element, możemy to zrobić, ale nie dla ostatniego elementu listy. bool deleteThis ( NODE * p ) { NODE * tmp = p - > next ; if ( tmp != null ) { // czy nie ostatni element ? // kopiowanie wartości następnika p - > val = tmp - > val ; // kopiowanie wskaźnika następnika p - > next = tmp - > next ; delete tmp ; return true ; // sukces } return false ; // porażka } Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista (implementacja wskaźnikowa) Przeglądanie/operacja wykonywana na wszystkich elementach listy Implementacja tablicowa Visit ( NODE list [] , int first ) { while ( first > -1) { do something with list [ first ]. val ; first = list [ first ]. next ; } } Implementacja wskaźnikowa Visit ( NODE * head ) { NODE * tmp = head ; while ( tmp != null ) { do something with tmp - > val ; tmp = tmp - > next ; } } Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista (implementacja wskaźnikowa) Wyszukiwanie elementu o wartości x Implementacja tablicowa int find ( NODE list [] , int first , T x ) { while ( first > -1) { if ( list [ first ]. val == x ) return first ; else first = list [ first ]. next ; } return -1; // nie znaleziono } Implementacja wskaźnikowa NODE * find ( NODE * head , T x ) { NODE * tmp = head ; while ( tmp != null ) { if ( tmp - > val == x ) return tmp ; else tmp = tmp - > next ; } return null ; // nie znaleziono } Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista Warianty Lista dwukierunkowa (doubly-linked list) każdy element musi posiadać dodatkowy wskaźnik prev dodatkowy wskaźnik pokazujący na ostatni element listy tail łatwiejsze operacje wstawiania i usuwania elementów, większe zapotrzebowanie na pamięć Lista cykliczna (circularly-linked list) pierwszy i ostatni węzeł listy są połączone (są sąsiadami) może być jedno- lub dwukierunkowa Unrolled linked list — przechowuje wiele wartości w jednym węźle → zwiększenie lokalności danych Wartownik (sentinel) to sztuczny węzeł, który powala uprościć warunki brzegowe dotyczące ogona i głowy listy Z reguły wartownik->next == wartownik Użycie wartowników nie prowadzi zwykle do poprawy asymptotycznej złożoności operacji wykonywanych na liście lecz często prowadzi do zmniejszenia stałych współczynników Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista Przyspieszanie wyszukiwania w liście Poszukiwanie elementu w liście jest mało efektywne, ponieważ przeszukiwanie listy może być prowadzone tylko sekwencyjnie trzeba odwiedzić wszystkie elementy listy bezpośrednie zastosowanie szybszych metod lokalizowania elementów (np. przeszukiwanie binarne) przydatnych w strukturach danych o dostępie dowolnym są nieefektywne dla list Podstawową metodą przyspieszania wyszukiwania elementów na liści jest wykorzystanie uporządkowania listy względem wybranego klucza Listy uporządkowane Listy uporządkowane z przeskokami Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista uporządkowana Mniej operacji: zamiast insertAfter i insertBefore mamy tylko operację insert Najdogodniejszym sposobem utworzenia listy posortowanej jest zagwarantowanie, że po każdej operacji insert lista pozostaje uporządkowana + Wyszukiwanie elementu: lista jest przeglądana tylko tak daleko, jak długo elementy mają klucz mniejszy od poszukiwanego. W pesymistycznym przypadku trzeba odwiedzić wszystkie elementy listy. Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista uporządkowana Dodawanie elementu Dane wejściowe: lista posortowana względem danego klucza Uwagi: lista pusta lub jednoelementowa jest posortowana Dane wejściowe: lista posortowana względem danego klucza Wariant 1 Wstawienie na właściwą pozycję 1 Znajdź pozycję, na której powinien pojawić się nowy element by lista pozostała posortowana 2 Wstaw element na tą pozycję Wariant 2 Przywrócenie uporządkowania 1 Wstaw nowy element na początek listy 2 Przesuwaj ten element dalej tak długo jak następnik istnieje i jego klucz jest mniejszy od klucza elementu dodawanego Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista uporządkowana Dodawanie elementu – cd. Wariant 1: Przykład head 7 head head 2 2 2 4 4 4 8 8 7 null null 8 null Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista uporządkowana Dodawanie elementu – cd. Wariant 2: Przykład head 7 head head head head 2 2 7 2 2 4 4 2 7 4 8 8 null null 4 8 4 8 7 8 null null null Wstęp Statyczne struktury danych Dynamiczne struktury danych Listy uporządkowane z przeskokami Wprowadza się dodatkowe poziomy odnośników, pozwalających na przemieszczanie się po liście o więcej niż jeden element. Zaleta: przeszukiwanie listy nie musi być sekwencyjne Wstęp Statyczne struktury danych Dynamiczne struktury danych Listy uporządkowane z przeskokami Poszukiwanie elementu Poszukiwanie elementu W pierwszej kolejności przeszukiwanie prowadzone jest z wykorzystaniem najwyższego poziomu odnośników Jeśli osiągnięto koniec listy lub napotkano element z kluczem większym niż poszukiwany, wówczas poszukiwanie ponawiane jest od węzła poprzedzającego, ale z wykorzystaniem wskaźników poziomu o jeden niższego. Szukanie trwa aż do znalezienia elementu lub wykorzystania wszystkich poziomów poszukiwań Przykład: wyszukiwanie elementu z kluczem 5: Wstęp Statyczne struktury danych Dynamiczne struktury danych Lista Pozostałe uwagi Usprawnienia w implementacji listy Przydatna są operacje isEmpty oraz size. Operacja size może bazować na liczniku wstawień i usunięć Wprowadzenie dodatkowego wskaźnika tail przyspiesza dodawanie elementów na koniec listy Do usprawnienia przeglądania listy wygodnie jest wprowadzić sztuczny element znajdujący się za ostatnim elementem listy (porównaj rozwiązanie z wartownikiem). Wówczas dobrze jest wprowadzić dwie operacje na liście: last oraz end Wstęp Statyczne struktury danych Dynamiczne struktury danych Porównanie listy i tablicy Operacja Tablica Rozmiar Dostęp do elem. brzegowego Dostęp do elem. wewnętrznego Dostęp do elem. następnego Dostęp do elem. poprzedniego Wstawianie/usuwanie na początku Wstawianie/usuwanie na końcu Wstawianie/usuwanie wewnątrz Lokalność danych O(1) O(1) O(1) O(1) O(1) O(N) O(1) O(N) b. duża Lista jednk. dwuk. O(n) O(n) O(1) O(1) O(n) O(n) O(1) O(1) O(n) O(1) O(1) O(1) O(1) O(1) O(1) O(1) mała mała Dostęp sekwencyjny jest dużo szybszy w przypadku tablic (lokalność danych) Lista potrzebuje więcej pamięci (wskaźniki) do przechowywania tej samej ilości danych Rozmiar listy może się zmieniać dynamicznie Wstęp Statyczne struktury danych Dynamiczne struktury danych Stos Stos (stack) Stos - Last In First Out (LIFO) Def. to liniowa struktura danych, w której dane dokładane są na wierzchołek stosu (operacja push) oraz są pobierane (operacja pop) również z wierzchołka stosu Aby ściągnąć element ze stosu, należy najpierw po kolei ściągnąć wszystkie elementy znajdujące się nad nim Zastosowania: Obliczenia — odwrotna notacja polska (RPN) Pamięć programu (zmienne automatyczne, wywołania funkcji) Algorytmy parsingu, grafowe, ... Wstęp Statyczne struktury danych Dynamiczne struktury danych Stos Operacje Implementacja tablicowa pierwszy element tablicy = dno stosu ostatni aktywny element tablicy = wierzchołek stosu potrzebna dodatkowa zmienna przechowująca indeks wierzchołka Implementacja listowa głowa listy = wierzchołek stosu ogon listy = dno stosu Podstawowe operacje isEmpty() — O(1) T pop() — O(1) push(T) — O(1) Wstęp Statyczne struktury danych Dynamiczne struktury danych Kolejka (queue) Kolejka = First In First Out to liniowa struktura danych, w której dane dodawane są na końcu (tail) kolejki (operacja enqueue), a są usuwane (operacja dequeue) z początku (head) kolejki Zastosowania Obsługa zdarzeń Procesy kolejkowe Algorytmy grafowe, ... Wariant: kolejka dwustronna (dequeue), kolejka priorytetowa (priority queue) Wstęp Statyczne struktury danych Dynamiczne struktury danych Kolejka (queue) Implementacja tablicowa zorganizowana jako bufor cykliczny potrzebne dwie dodatkowe zmienne do przechowywania indeksów początku i końca kolejki Implementacja listowa głowa listy = koniec kolejki ogon listy = początek kolejki Podstawowe operacje isEmpty() — O(1) T dequeue() — O(1) enqueue(T) — O(1) Kolejka priorytetowa służy do przechowywania elementów zbioru, na którym określono relację porządku najczęściej implementowano jako kopiec lub tablica asocjacyjna Wstęp Statyczne struktury danych Dynamiczne struktury danych Drzewo Drzewo (tree) Drzewo jest hierarchiczną strukturą danych. Def. Drzewo jest to zbiór T jednego lub więcej elementów zwanych węzłami, takich że istnieje jeden wyróżniony węzeł zwany korzeniem drzewa i pozostałe węzły (z wyłączeniem korzenia) są podzielone na m ≥ 0 rozłącznych zbiorów T1 , . . . , Tm , z których każdy jest drzewem, zwanym poddrzewem korzenia. Pierwszy obiekt zwany jest korzeniem, kolejne obiekty traktowane są jako jego potomstwo: węzły. Liście to węzły nie mające potomstwa Droga w drzewie – sekwencja węzłów w drzewie odpowiadających przejściu w kierunku od korzenia do liścia Pojęcia: rodzic, przodek, potomek, rodzeństwo (dwa węzły są rodzeństwem, gdy mają tego samego ojca) Warianty: drzewa AVL, drzewa czerwono-czarne, BST, ... Wstęp Statyczne struktury danych Dynamiczne struktury danych Drzewa binarne Drzewo binarne jest skończonym zbiorem węzłów, który jest albo pusty, albo zawiera korzeń oraz dwa drzewa binarne Każdy węzeł przechowuje dwa wskaźniki: do lewego poddrzewa left i prawego poddrzewa right root jest wskaźnikiem do drzewa Jeśli root = Λ - drzewo puste wpp root jest adresem korzenia drzewa, left(root) wskazuje lewe poddrzewo, right(root) wskazuje prawe poddrzewo Implementacja wskaźnikowa struct NODE { T val ; // wartość NODE * left ; // wskaźnik do lewego syna NODE * right ; // wskaźnik do prawego syna } * root = null ; // początkowo drzewo jest puste Wstęp Statyczne struktury danych Dynamiczne struktury danych Drzewa binarne - operacje Podstawowe operacje dla drzew wyliczenie wszystkich elementów drzewa ("przejście" drzewa) wyszukanie elementu dodanie nowego elementu/poddrzewa w określonym miejscu drzewa usunięcie elementu/poddrzewa Wstęp Statyczne struktury danych Dynamiczne struktury danych Przechodzenie drzewa binarnego Jest to systematyczne przeglądanie węzłów w taki sposób, ze każdy węzeł jest odwiedzony dokładnie jeden raz Przejście drzewa wyznacza porządek liniowy w drzewie 6 sposób przechodzenia drzewa VLR, LVR, LRV, VRL, RVL, RLV gdzie: Visit = odwiedź węzeł, Left = idź w lewo, Right = idź w prawo W szczególności wyróżnia się trzy pierwsze: VLR pre-order, wzdłużny: korzeń, lewe poddrzewo, prawe poddrzewo LVR in-order, poprzeczny: lewe poddrzewo, korzeń, prawe poddrzewo LRV post-order, wsteczny: lewe poddrzewo, prawe poddrzewo, korzeń preorder ( NODE * root ) { if (6= root ) return ; visit ( root - > val ) if ( root - > left ) preorder ( root - > left ) ; if ( root - > right ) ; preorder ( root - > right ) ; } inorder ( NODE * root ) { if (6= root ) return ; if ( root - > left ) inorder ( root - > left ) ; visit ( root - > val ) ; if ( root - > right ) inorder ( root - > right ) ; } postorder ( NODE * root ) { if (6= root ) return ; if ( root - > left ) postorder ( root - > left ) ; if ( root - > right ) postorder ( root - > right ) ; visit ( root - > val ) ; } Wstęp Statyczne struktury danych Dynamiczne struktury danych Przykład Pre-order: A B D C E G F H I In-order: D B A E G C H F I Post-order: D B G E H I F C A Wstęp Statyczne struktury danych Dynamiczne struktury danych Porządek in-order - algorytm nierekurencyjny inorder ( NODE * root ) { S = Λ; // S - stos p = root ; // p - zmienna pomocnicza while (1) { while ( p 6= Λ) { push (S , p ) ; p = p - > left ; } if ( S =Λ) return ; // koniec algorytmu p = pop ( S ) ; visit ( p ) ; p = p - > right ; } Wstęp Statyczne struktury danych Dynamiczne struktury danych Drzewa poszukiwań binarnych - BST Binarne drzewo poszukiwań (Binary search tree) Binarne drzewo poszukiwań to drzewo binarne o następującej własności każdy element binarnego drzewa poszukiwań ma tę własność, że jego lewostronne potomstwo jest mniejsze bądź równe co do wartości od tego elementu, a prawostronne potomstwo jest większe bądź równe (drzewo BST) Własność binarnego drzewa binarnego dla każdego węzła x drzewa zachodzi: x->val ≥ x->left->val oraz x->val ≤ x->right->val Wstęp Statyczne struktury danych Dynamiczne struktury danych Implementacja BST Implementacja wskaźnikowa struct BST_N { T val ; // wartość BST_N * left ; // wskaźnik do lewego syna BST_N * right ; // wskaźnik do prawego syna BST_N * parent ; // opcjonalny wskaźnik do ojca } * root = null ; // początkowo drzewo jest puste Implementacja tablicowa struct BST_N { T val ; // wartość integer left ; // wskaźnik do lewego syna integer right ; // wskaźnik do prawego syna integer parent ; // opcjonalny wskaźnik do ojca } tree [ N ]; root = 0; // początkowo drzewo jest puste Wstęp Statyczne struktury danych Dynamiczne struktury danych Operacje na BST Przechodzenie drzewa Wyszukiwanie węzła o podanym kluczu największego/najmniejszego następnika/poprzednika węzła Wstawianie węzła do drzewa Usuwanie węzła/podrzewa Wstęp Statyczne struktury danych Dynamiczne struktury danych Przechodzenie BST I Własność BST umożliwia wypisanie wszystkich znajdujących się w nim elementów w uporządkowany sposób Wykorzystujemy algorytm przechodzenia drzewa metodą inorder (przechodzenie poprzeczne) inorder ( BST_N * root ) { if (¬ root ) stop ; if ( root - > left ) inorder ( root - > left ) ; wypisz ( root - > val ) ; if ( root - > right ) inorder ( root - > right ) ; } Złożoność: O(n), n — liczba węzłów drzewa Wstęp Statyczne struktury danych Dynamiczne struktury danych Przechodzenie BST II Niekiedy procedura przechodzenia drzewa BST nazywana jest "sortowaniem" drzewiastym Algorytm "sortowania" drzewiastego TreeSort ( val arr []) { BST_N * root ← arr ; // przekształć listę wejściową w BST inorder ( root ) ; Procedura ta nie zmienia porządku w tablicy, a jedynie wypisuje elementy w sposób uporządkowany Wstęp Statyczne struktury danych Dynamiczne struktury danych Wyszukiwanie węzła w BST Wersja rekurencyjna BST_N * find ( NODE * root , T x ) { if (¬ root ) return 0; if ( root - > val = x ) return root ; if ( root - > val > x ) return find ( root - > left , x ) ; else return find ( root - > right , x ) ; } Wersja iteracyjna NODE * find ( BST_N * root , T x ) { while ( root ∧ root - > val 6= x ) if ( root - > val > x ) root = root - > left ; else root = root - > right ; return root ; } Złożoność obliczeniowa: O(h), gdzie h jest wysokością drzewa Wstęp Statyczne struktury danych Dynamiczne struktury danych Wyszukiwanie minimum i maksimum w BST Wyszukiwanie minimum: należy przejść od korzenia do najbardziej lewego liścia BST_N * min ( BST_N * root ) { while ( root - > left ) root = root - > left ; return root ; } Wyszukiwanie maksimum: należy przejść od korzenia do najbardziej prawego liścia BST_N * max ( BST_N * root ) { while ( root - > right ) root = root - > rigth ; return root ; } Złożoność obliczeniowa: O(h), gdzie h jest wysokością drzewa Wstęp Statyczne struktury danych Dynamiczne struktury danych Następnik i poprzednik w BST Struktura BST umożliwia wyznaczenie następnika i poprzednika bez konieczności porównywania kluczy Konieczne jest wtedy przechowywanie w każdym węźle wskaźnika do ojca NODE* parent Jeśli wszystkie klucze są różne, to następnikiem węzła x jest węzeł o najmniejszym kluczu większym od x->val poprzednikiem węzła x jest węzeł o największym kluczu mniejszym od x->val Wstęp Statyczne struktury danych Dynamiczne struktury danych Następnik w BST Następnik: jeżeli jest prawe poddrzewo, to następnikiem węzła x jest najmniejszy element tego poddrzewa jeżeli brak prawego poddrzewa, to następnikiem węzła x jest jego najniższy przodek, którego lewy syn jest przodkiem x Funkcja next BST_N * next ( BST_N * x ) { if (x - > right ) // jeżeli jest prawe poddrzewo return min (x - > right ) ; // najmniejszy na prawo BST_N * y = x - > parent ; // brak prawego poddrzewa while ( y ∧ x = y - > right ) { // cofamy się do góry x = y; y = y - > parent ; } return y ; } Złożoność obliczeniowa: O(h), gdzie h jest wysokością drzewa Wstęp Statyczne struktury danych Dynamiczne struktury danych Poprzednik w BST Poprzednik: jeżeli jest lewe poddrzewo, to następnikiem węzła x jest największy element tego poddrzewa jeżeli brak lewego poddrzewa, to następnikiem węzła x jest jego najniższy przodek, którego prawy syn jest przodkiem x Funkcja prev BST_N * prev ( BST_N * x ) { if (x - > left ) // jeżeli jest lewe poddrzewo return max (x - > left ) ; // największy na prawo BST_N * y = x - > parent ; // brak prawego poddrzewa while ( y ∧ x = y - > left ) { // cofamy się do góry x = y; y = y - > parent ; } return y ; } Złożoność obliczeniowa: O(h), gdzie h jest wysokością drzewa Wstęp Statyczne struktury danych Dynamiczne struktury danych Wstawianie węzła w BST insert - BST ( BST_N * root , BST_N * x ) { if (¬ root ) { // jeżeli drzewo było puste root = x ; stop ; // x - > left = x - > right = NULL ; } BST_N * par = 0; BST_N * son = root ; while ( son ) { par = son ; if ( par - > val > x - > val ) son = par - > left ; else son = par - > right ; } x - > parent = par ; // x - > left = x - > right = 0; if ( par - > val > x - > val ) par - > left = x ; // x - lewym synem else par - > right = x ; // x - prawym synem } Wstęp Statyczne struktury danych Dynamiczne struktury danych Usuwanie węzła w BST (3 przypadki) Wstęp Statyczne struktury danych Dynamiczne struktury danych Usuwanie węzła w BST remove - BST ( BST_N * root , BST_N * z ) { BST_N * y ; if (z - > left ∧ z - > right ) y = next ( z ) ; // do usunięcia else y = z ; BST_N * x ; if (y - > left ) x = y - > left ; // sprawdzenie , czy y ma lewego syna else x = y - > right ; if ( x ) x - > parent = y - > parent ; // podpinamy x do ojca y - ka if (y - > parent ) { // ojciec y - ka pokaże na x if ( y = y - > parent - > left ) y - > parent - > left = x ; else y - > parent - > right = x ; } else root = x ; // jeżeli z = y jest korzeniem if ( y 6= z ) // nadpisanie usuniętego z z - > val = y - > val ; delete y ; // fizyczne usunięcie węzła } Usuwany element musi być zastąpiony przez swój następnik (w prawym poddrzewie) lub swój poprzednik (w lewym poddrzewie) Wstęp Statyczne struktury danych Dynamiczne struktury danych Inne rodzaje drzew Drzewa BST z powtarzającymi się kluczami Drzewa pozycyjne: porządek leksykograficzny klucz każdego węzła można jednoznacznie wyznaczyć na podstawie ścieżki od korzenia do tego węzła ⇒ nie ma potrzeby przechowywania klucza w węźle Wstęp Statyczne struktury danych Dynamiczne struktury danych Drzewa zrównoważone W drzewach BST pesymistyczny koszt operacji dostępu (wyszukanie, wstawienie, usunięcie) jest proporcjonalny do wysokości drzewa - może być zatem liniowy Kształt drzewa (czyli również jego wysokość) zależy od ciągu wykonywanych na nim operacji Potrzebny jest dodatkowy mechanizm, który zapewni zrównoważenie drzewa, tzn. pomimo zmiany struktury drzewa, jego wysokość zawsze pozostaje logarytmiczna względem jego rozmiaru Wstęp Statyczne struktury danych Dynamiczne struktury danych Drzewa AVL Drzewo AVL (Gieorgij Adelson-Wielskij, Jewgienij Łandis) to drzewo BST spełniające dodatkowo następujący warunek zrównoważenia: w każdym węźle wysokości obu jego poddrzew różnią się co najwyżej o 1 Implementacja: w każdym węźle przechowywany jest dodatkowy atrybut (współczynnik zrównoważenia), przyjmujący wartości: -1 jeśli lewe poddrzewo jest o 1 wyższe niż prawe 0 jeśli oba poddrzewa są takiej samej wysokości +1 jeśli prawe poddrzewo jest o 1 wyższe niż lewe struct AVL_N { struct BST_N ; integer balance ; } * root = NULL ; // początkowo drzewo jest puste Wstęp Statyczne struktury danych Dynamiczne struktury danych Operacje na drzewie BST Operacje wyszukiwania są identyczne jak dla drzewa BST Operacje wstawiania i usuwania elementu są bardziej skomplikowane - zazwyczaj konieczna jest wtedy zmiana struktury drzewa tak, aby drzewo nadal pozostało zrównoważone Wstawienie węzłów o kluczach 9 lub 11 nie zmieni zrównoważenia drzewa Wstawienie węzłów o kluczach 1, 3, 5 lub 7 spowoduje konieczność wyważenia drzewa Do zmiany kształtu drzewa, bez zaburzania własności drzewa BST, służą operacje rotacji Wstęp Statyczne struktury danych Dynamiczne struktury danych Rotacje Rozróżniamy dwa istotne przypadki rotacji ⇒ dwie operacje rotacji Rotacja pojedyncza (uczestniczą dwa węzły) Rotacja podwójna (uczestniczą trzy węzły) Wstęp Statyczne struktury danych Dynamiczne struktury danych Rotacja pojedyncza rot1left ( AVL_N * root , AVL_N * x ) { AVL_N * y = x - > right ; x - > balance = y - > balance = 0; x - > right = y - > left ; x - > right - > parent = x ; y - > parent = x - > parent ; if (x - > parent = NULL ) root = y ; else if (x - > parent - > left = x ) x - > parent - > left = y ; else x - > parent - > right = y ; y - > left = x ; x - > parent = y ; } rot1right ( AVL_N * root , AVL_N * y ) { AVL_N * x = y - > left ; x - > balance = y - > balance = 0; y - > left = x - > right ; y - > left - > parent = y ; x - > parent = y - > parent ; if (y - > parent = NULL ) root = x ; else if (y - > parent - > left = y ) y - > parent - > left = x ; else y - > parent - > right = x ; x - > right = y ; y - > parent = x ; } Wstęp Statyczne struktury danych Dynamiczne struktury danych Rotacja podwójna rot2left ( AVL_N * root , AVL_N * A ) { AVL_N * C =A - > right , * B =C - > left ; A - > right =B - > left ; // beta A - > right - > parent = A ; // beta C - > left =B - > right ; // gamma C - > left - > parent = C ; // gamma B - > parent =A - > parent ; if (A - > parent = NULL ) root = B ; else if (A - > parent - > left = A ) A - > parent - > left = B ; else A - > parent - > right = B ; B - > left = A ; B - > right = C ; A - > parent =C - > parent = B ; if (B - > balance =1) { A - > balance = -1; C - > balance =0;} else { A - > balance =0; C - > balance =+1;} B - > balance =0; } rot2right ( AVL_N * root , AVL_N * C ) { AVL_N * A =C - > left , * B =A - > right ; A - > right =B - > left ; // beta A - > right - > parent = A ; // beta C - > left =B - > right ; // gamma C - > left - > parent = C ; // gamma B - > parent =C - > parent ; if (C - > parent = NULL ) root = B ; else if (C - > parent - > left = C ) C - > parent - > left = B ; else C - > parent - > right = B ; B - > left = A ; B - > right = C ; A - > parent =C - > parent = B ; if (B - > balance =1) { A - > balance = -1; C - > balance =0;} else { A - > balance =0; C - > balance =+1;} B - > balance =0; } Wstęp Statyczne struktury danych Dynamiczne struktury danych Rotacja podwójna rot2left ( AVL_N * root , AVL_N * A ) { integer balB = A - > right - > left - > balance ; rot1right ( root , A - > right ) ; rot1left ( root , A ) ; if ( balB = 1) { B - > left - > balance = -1; B - > right - > balance = 0; } else { B - > left - > balance = 0; B - > right - > balance = +1; } B - > balance = 0; } rot2right ( AVL_N * root , AVL_N * C ) { integer balB = A - > left - > right - > balance ; rot1left ( root , C - > left ) ; rot1right ( root , C ) ; if ( balB = 1) { B - > left - > balance = -1; B - > right - > balance = 0; } else { B - > left - > balance = 0; B - > right - > balance = +1; } B - > balance = 0; } Wstęp Statyczne struktury danych Dynamiczne struktury danych Wstawianie węzłów w drzewie AVL Co może się zdarzyć po wstawieniu nowego w˛ezła do drzewa? (Wstawiamy węzeł do lewego poddrzewa L): jeżeli h(L) = h(R), to po wstawieniu L i P będą poddrzewami o różnej wysokości, ale kryterium wyważenia będzie wciąż spełnione jeżeli h(L) < h(R), to poddrzewa L i P uzyskują tę samą wysokość jeżeli h(L) > h(R), to kryterium wyważenie nie jest spełnione i drzewo musi być przebudowane Wstęp Statyczne struktury danych Dynamiczne struktury danych Wstawianie węzłów w drzewie AVL Proces wstawiania węzła 1 Schodzimy po ścieżce przeszukiwania drzewa 2 Wstawiamy nowy liść, wyznaczamy współczynnik wyważenia Wracamy w kierunku korzenia, aktualizując współczynniki wyważenia 3 Jeżeli wysokość drzewa nie zmieniła się, to kończymy W przeciwnym razie kontynuujemy marsz w górę drzewa 4 Jeżeli został naruszony warunek zrównoważenia, to przywracamy go za pomocą rotacji Wstęp Statyczne struktury danych Dynamiczne struktury danych Wstawianie węzłów w drzewie AVL insert - AVL ( AVL_N * root , AVL_N * x , integer bal ) { if (¬root ) { root = x ; root - > balance = 0; bal = 1; // tu ’ + ’ czy ’ - ’ nie ma znaczenia return ; } if (x - > val < root - > val ) { // wstawiamy do lewego poddrzewa insert ( root - > left , x , bal ) ; switch ( root - > balance ) { case 0: if ( bal ) root - > balance = -1; break ; case 1: if ( bal ) root - > balance = 0; break ; default : if ( bal == -1) rot1right ( root - > parent , root ) ; else rot2right ( root - > parent , root ) ; root - > balance = 0; } } else { // wstawiamy do prawego poddrzewa insert ( root - > right , x , bal ) ; switch ( root - > balance ) { case 0: if ( bal ) root - > balance = 1; break ; case -1: if ( bal ) root - > balance = 0; break ; default : if ( bal == -1) rot2left ( root - > parent , root ) ; else rot1left ( root - > parent , root ) ; root - > balance = 0; } } bal = root - > balance ; } Wstęp Statyczne struktury danych Dynamiczne struktury danych Usuwanie węzłów w drzewie AVL Algorytm usuwania węzła w drzewie AVL bazuje na algorytmie usuwania węzła dla drzewa BST, choć musi być zmodyfikowany Po zastąpieniu usuniętego węzła jego następnikiem Z miejsca, skąd pobrano następnik, należy przejść w stronę korzenia, przywracając zrównoważenie wierzchołków używając rotacji 1 2 3 Odtwarzanie może być zatrzymane, jeśli współczynnik wyważenia zostaje zmieniony na -1 lub 1; oznacza to, że wysokość poddrzewa pozostaje niezmieniona Zmiana współczynnika wyważenia na 0 oznacza zmniejszenie wysokości poddrzewa, aktualizowanie współczynników musi być kontynuowane Jeśli współczynnik zostanie zmieniony na -2 lub 2, to wykonywana jest rotacja w celu przywrócenia struktury AVL Tym razem może się zdarzyć, że trzeba będzie dokonywać rotacji na tej drodze, aż do samego korzenia włącznie Wstęp Statyczne struktury danych Dynamiczne struktury danych Drzewa AVL Nawet w najgorszym przypadku można w drzewie AVL wykonać następujące operacje wyszukiwanie wstawianie węzła usuwanie węezła Drzewa AVL sprawdzają się najlepiej w sytuacjach, gdy najczęstszą operacją jest wyszukiwanie Twierdzenie (Adelson-Wielskij, Łandis) Niezależnie od liczby węzłów, drzewo AVL nie będzie nigdy wyższe o więcej niż 45% od swego dokładnie wyważonego odpowiednika Wysokość drzewa AVL h(n) o n węzłach log(n + 1) ≤ h(n) ≤ 1.4404 log(n + 2) − 0.328 Wstęp Statyczne struktury danych Dynamiczne struktury danych Drzewa czerwono-czarne Drzewo czerwono-czarne RBT (Rudolf Bayer, 1972) to drzewo BST, w którym: 1 każdy węzeł jest czerwony lub czarny 2 korzeń drzewa jest czarny 3 każdy liść (przyjmuje się, że liśćmi są elementy NIL) jest czarny 4 każdy czerwony węzeł ma czarne dzieci 5 każda prosta ścieżka z ustalonego węzła do liścia (w dół drzewa) ma tyle samo czarnych węzłów (gdyby miała tyle samo zarówno czerwonych, jak i czarnych węzłów, drzewo miałoby wszystkie gałęzie równej długości) Wstęp Statyczne struktury danych Dynamiczne struktury danych Drzewa czerwono-czarne struct RBT_N { struct BST_N ; string color ; // uwaga : wystarczy jeden dodatkowy bit } * root = NULL ; // początkowo drzewo jest puste Wartość NIL reprezentuje jeden czarny węzeł-wartownik Istotne informacje zawierają jedynie węzły wewnętrzne drzewa RB Wstęp Statyczne struktury danych Dynamiczne struktury danych Drzewa czerwono-czarne Liczbę czarnych węzłów na dowolnej ścieżce z węzła x (wykluczając węzeł x) do liścia nazywamy czarną wysokością węzła bh(x) Czarna wysokość drzewa RB to czarna wysokość jego korzenia Wstęp Statyczne struktury danych Dynamiczne struktury danych Drzewa czerwono-czarne Wysokość RBT o n węzłach wewnętrznych wynosi co najwyżej 2 log(n + 1) Dowód Każde poddrzewo o korzeniu x ma co najmniej 2bh(x ) − 1 węzłów wewnętrznych 1 2 jeśli x ma wysokość 0 (jest liściem), to poddrzewo zawiera co najmniej 20 − 1 = 0 węzłów jeśli x ma 2 synów, to czerwony syn ma wysokość bh(x) a czarny bh(x)−1; wysokość syna jest mniejsza niż wysokość x, zatem każde z poddrzew ma co najmniej 2bh(x )−1 − 1 węzłów wewnętrznych; stąd poddrzewo o korzeniu x ma co najmniej 2(2bh(x )−1 − 1) + 1 = 2bh(x ) − 1 węzłów wewnętrznych Jeśli h jest wysokością drzewa, to z własności (4) wynika, że co najmniej połowa węzłów na ścieżce korzeń-liść jest czarna ⇒ czarna wysokość drzewa wynosi co najmniej h/2, a zatem n ≥ 2h/2 − 1, stąd teza Wstęp Statyczne struktury danych Dynamiczne struktury danych Drzewa czerwono-czarne. Operacje ⇒ Operacje wyszukiwania działają w drzewie RBT w czasie O(log n) Przewaga drzew RBT nad drzewami AVL polega na tym, że przywrócenie własności RBT po wstawieniu/usunięciu węzła wymagają co najwyżej dwóch rotacji Operacje wyszukiwania są identyczne jak dla drzewa BST Operacje wstawiania i usuwania elementu są bardziej skomplikowane - zazwyczaj konieczna jest wtedy zmiana struktury drzewa tak, aby drzewo nadal spełniało warunki drzewa RBT Operacje wstawiania i usuwania mają pesymistyczną złożoność obliczeniową O(log n) Wstęp Statyczne struktury danych Dynamiczne struktury danych Wstawianie węzła do drzewa RBT Wstawiamy węzeł analogicznie jak dla drzewa BST Kolorujemy wstawiony węzeł na czerwono Przekolorowujemy węzły tak, aby przywrócić własność drzewa RBT Rozpatrywane są 3 przypadki. Przypadek 1 odróżniamy od przypadków 2 i 3, rozpatrując kolor brata ojca węzła, który wstawiamy (x) Wstęp Statyczne struktury danych Dynamiczne struktury danych Wstawianie węzła do drzewa RBT Przypadek 1: brat y ojca x jest czerwony: Węzeł C (równy węzłowi x w nowej iteracji) jest czerwony Węzeł C → parent jest niezmieniony Naprawiono zaburzenie własności (4), ale własność (2) może być dalej zaburzona Wstęp Statyczne struktury danych Dynamiczne struktury danych Wstawianie węzła do drzewa RBT Przypadek 2: brat y ojca x jest czarny i x jest prawym synem ⇒ sprowadzamy do Przypadku 3 przez lewą rotację - zachowana jest własność (5) Przypadek 3: brat y ojca x jest czarny i x jest lewym synem: α, β, γ mają czarne korzenie - własność (4) δ też ma czarny korzeń (patrz Przypadek 1) Po wykonaniu tego kroku przerywana jest pętla for Wstęp Statyczne struktury danych Dynamiczne struktury danych Wstawianie węzła do drzewa RBT insert - RBT ( RBT_N * root , RBT_N x ) { insert - BST ( root , x ) while ( x 6= root and x - > parent - > color = Red ) { if (x - > parent = x - > parent - > parent - > left ) { y ← x - > parent - > parent - > right if (y - > color = Red ) { \\ Przypadek 1 x - > parent - > color ← Black y - > color ← Black x - > parent - > parent ← Red x ← x - > parent - > parent } else { if ( x = x - > parent - > right ) { \\ Przypadek 2 x ← x - > parent rot1left ( root , x ) } x - > parent - > color ← Black \\ Przypadek 3 x - > parent - > parent - > color ← Red rot1right ( root ,x - > parent - parent ) } } else { // tak samo jak dla if z zamienionymi rolami " right " i " left " } } root - > color ← Black } Wstęp Statyczne struktury danych Dynamiczne struktury danych Usuwanie węzła z drzewa RBT Usuwamy węzeł analogicznie jak dla drzewa BST Przekolorowujemy węzły tak, aby przywrócić własność drzewa RBT Rozpatrujemy 4 przypadki, w każdym z nich usuwany węzeł jest oznaczony jako x Przypadek 1 zachodzi, jeśli węzeł w, brat węzła x, jest czerwony Przypadki 2, 3 i 4 zachodzą, jeśli węzeł w jest czarny i różnią się od siebie tym, jakie kolory mają synowie w Wstęp Statyczne struktury danych Dynamiczne struktury danych Usuwanie węzła z drzewa RBT Przypadek 1: brat w węzła x jest czerowny Węzeł w musi mieć czarnych synów ⇒ można zamienić kolory w i x→parent i wykonać lewą rotację w x→parent Nowy brat węzła x, jeden z synów w, jest teraz czarny ⇒ udało się sprowadzić przypadek 1 do jednego z przypadków 2,3 lub 4 Wstęp Statyczne struktury danych Dynamiczne struktury danych Usuwanie węzła z drzewa RBT Przypadek 2: węzeł w jest czarny i obaj jego synowie są koloru czarnego w jest czarny ⇒ usuwamy po jednej "czarnej jednostce" z x oraz w x pozostaje czarny, w staje się czerwony x->parent otrzymuje dodatkową "czarną jednostkę" jeśli Przypadek 2 powstaje z Przypadku 1, to color nowego węzła wskazywanego przez x jest czerwony, a ponieważ x->parent był czerwony, wykonanie pętli while zostaje przerwane Wstęp Statyczne struktury danych Dynamiczne struktury danych Usuwanie węzła z drzewa RBT Przypadek 3: węzeł w jest czarny, jego prawy syn jest czarny, a lewy syn jest czerwony zamieniamy kolory w o jego lewego syna w->left i wykonujemy prawą rotację dla w nowy brat w węzła x jest teraz czarny, a jego prawy syn czerwony ⇒ przekształcono Przypadek 3 do Przypadku 4 Wstęp Statyczne struktury danych Dynamiczne struktury danych Usuwanie węzła z drzewa RBT Przypadek 4: brat w węzła x jest czarny, prawy syn węzła w jest czerwony Przekolorowujemy węzły, wykonujemy lewą rotację dla węzła x->parent ⇒ możliwe stało się usunięcie nadmiarowej "czarnej jednostki" w x Przypisanie zmiennej x wskazania na korzeń drzewa przerywa wykonanie pętli while Wstęp Statyczne struktury danych Dynamiczne struktury danych Usuwanie węzła z drzewa RBT Procedura zbliżona do procedury usuwania węzła w drzewie BST Przywrócenie własności drzewa RB następuje w procedurze remove-RBT-FixUp remove - RBT ( RBT_N * root , RBT_N * z ) { if (z - > left = Λ ∨ z - > right = Λ) y ← z else y ← next ( z ) if (y - > left 6= Λ) x ← y - > left else x ← y - > right x - > parent ← y - > parent if (y - > parent = Λ) root ← x else if ( y = y - > parent - > left ) y - > parent - > left ← x else y - > parent - > right ← x if ( y 6= z ) z - > val ← y - > val if (y - > color = Black ) remove - RBT - FixUp ( root , x ) delete y } Wstęp Statyczne struktury danych Dynamiczne struktury danych Usuwanie węzła z drzewa RBT remove - RBT - FixUp ( RBT_N * root , RBT_N * x ) { while ( x 6= root ∧ x - > color = Black ) { if ( x = x - > parent - > left ) { w ← x - > parent - > right if (w - > color = Red ) { // Przypadek 1 w - > color ← Black x - > parent - > color ← Red rot1left ( root , x - > parent ) w ← x - > parent - > right } if (w - > left - > color = Black ∧ w - > right - > color = Black ) { w - > color ← Red // Przypadek 2 x ← x - > parent } else { if (w - > right - > color = Black ) { // Przypadek 3 w - > left - > color ← Black w - > color ← Red rot1right ( root , w ) w ← x - > parent - > right } w - > color ← x - > parent - > color // Przypadek 4 x - > parent - > color ← Black w - > right - > color ← Black rot1left ( root , x - > parent ) x ← root } } else { // tak jak dla if tylko zamień rolami " right " i " left "} } x - > color ← Black }