Struktury danych

advertisement
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
}
Download