Algorytmy i struktury danych Równoważenie drzew Drzewa czerwono-czarne Drzewa kontekstowe B-drzewa Drzewa zrównoważone  Czas operacji na BST jest proporcjonalny do wysokości drzewa  Drzewo doskonale zrównoważone – dla dowolnego wierzchołka.

Download Report

Transcript Algorytmy i struktury danych Równoważenie drzew Drzewa czerwono-czarne Drzewa kontekstowe B-drzewa Drzewa zrównoważone  Czas operacji na BST jest proporcjonalny do wysokości drzewa  Drzewo doskonale zrównoważone – dla dowolnego wierzchołka.

Algorytmy i struktury
danych
Równoważenie drzew
Drzewa czerwono-czarne
Drzewa kontekstowe
B-drzewa
Drzewa zrównoważone

Czas operacji na BST jest proporcjonalny do
wysokości drzewa
 Drzewo doskonale zrównoważone – dla dowolnego
wierzchołka rozmiar lewego i prawego poddrzewa
różnią się najwyżej o 1.
 Drzewo zrównoważone – długość dowolnej scieżki z
węzła do liści różni się od wysokości tego węzła
najwyżej o 1.
 Drzewo w przybliżeniu zrównoważone – długość
dowolnej scieżki z węzła do liści różni się od
wysokości tego węzła najwyżej 2 razy.
Przykłady drzew zrównoważonych

Drzewa AVL
 Drzewa czerwono czarne
 B-drzewa
Drzewo czerwono-czarne
Każdy węzeł jest czerwony lub czarny
2) Każdy NULL jest czarny
3) Jeżeli węzeł jest czerwony to obaj jego synowie są
czarni
4) Każda ścieżka z ustalonego węzła do liścia-NULL
ma tyle samo czarnych węzłów (czarna wysokość)
1)
Drzewo czerwono-czarne (RBT)
30
25
41
15
8
18
10 NULL
4
1
28
NULL
NULL
NULL
27
35
29
NULL
NULL
26
20
NULL
45
33
NULL
36
40
NULL
NULL
NULL NULL
NULL
NULLNULLNULL NULL
NULL
NULL

NULL
37
Wysokość drzewa RBT  2lg(n+1)
NULL
Równowaga w RBT
Każda ścieżka z ustalonego wezła do liścia-NULL ma
tyle samo czarnych węzłów
NIE może być węzła, który
nie ma obu potomków i w
poddrzewie ma
czarne węzły
NULL
NULL
Właściwości RBT
Wysokość drzewa RBT jest  2lg(n+1)
Search  O(log(n))
Min  O(log(n))
Max  O(log(n))
Succesor  O(log(n))
Predecesor  O(log(n))
Definicja węzła
class NODE :
data = None
color = BLACK
left = None
right = None
parent = none
Operacja rotacji
RightRotate(T,y)
y
x
y
C
LeftRotate(T,x)
A
B
x
A
B
C
LeftRotate
x
y
A
def LeftRotate(root, x): # zmiana na right
C
B
y = x.right
# x.left
if (y==None): return
x.right = y.left
# x.right
if (y.left!=None): y.left.parent = x # y.right
y
y.parent = x.parent
if x.parent==None:
x
root = y
elif x==x.parent.left:
x.parent.left = y
A
B
else:
x.parent.right = y
y.left = x
# y.right
x.parent = y
return root
C
Wstawianie węzła
Narusza długość
czarnych ścieżek
Nie narusza długości
czarnych ścieżek
Ale może naruszyć zasadę 3
Wstawianie węzła
•
•
•
Wstaw węzeł x do drzewa (liść)
Pokoloruj x na czerwono
Uporządkuj drzewo
(naruszona może być własność 3):
Porządkowanie RBT po INS - 1
15
20
4
8
1
6
25
10
Przypadek 1
stryj
20
4
5
x
15
8
1
6
nowy x
10
5
Jeżeli stryj(x) jest czerwony
przemaluj wierzchołki dziadek(x), ojciec(x), stryj(x)
wznów operację od dziadka, tj. podstaw x = dziadek(x)
25
Porządkowanie RBT po INS - 2
15
20
4
8
1
6
5
stryj
15
25
x
14
8
nowy x
4
10
Przypadek 2
stryj
10
6
1
5
Jeżeli x jest synem z tej strony co stryj(x) (kierunek )
Przyjmij x = ojciec(x) obróć drzewo w przeciwną,
tj. ’ względem nowego x
Uwaga po tej operacji x znajduje się po przeciwnej stronie swego
ojca niż stryj swojego
15
Porządkowanie RBT po INS - 3
11
14
7
x
2
8
stryj
15
5
1
4
7
x
11
2
Przypadek 3
5
1
14
8
4
Jeżeli x jest synem z przeciwnej strony niż stryj(x)
Przemaluj wierzchołki ojciec(x) i dziadek(x), a następnie, obróć
drzewo względem dziadek(x) w stronę stryja tj. 
15
Porządkowanie drzewa po INS
Z: korzeń drzewa jest czarny (czemu nie ?)
•
Dopóki ojciec(x) jest czerwony i nie doszliśmy do
korzenia
Jeżeli stryj(x) jest czerwony
1.
przemaluj wierzchołki dziadek(x), ojciec(x), stryj(x) i wznów
operację od dziadka, tj. podstaw x = dziadek(x)
W przeciwnym razie
Jeżeli x jest synem z tej strony co stryj(x) (kierunek )
2. Przyjmij x = ojciec(x) obróć drzewo w przeciwną tj ’
względem x (nowego)
3. Przemaluj ojciec(x) i dziadek(x), a następnie, obróć drzewo
względem dziadek(x) w stronę stryja tj. 
•
Upewnij się czy korzeń drzewa jest dalej czarny
Wstawianie węzła – implem.
def RBTInsert(root, x):
Insert(root, x)
x.color = RED
# zalozenie: root.color == BLACK
while x != root and x.parent.color == RED:
if x.parent == x.parent.parent.left:
#Porządkowanie dla ojca po lewej
else:
#Porządkowanie dla ojca po prawej
root.color = BLACK
return root
Porządkowanie dla ojca po lewej
# Porządkowanie dla ojca po lewej
uncle = x.parent.parent.right
if GetColor(uncle) == RED :
x.parent.color = BLACK
# przypadek 1
uncle.color = BLACK
x.parent.parent = RED
x = x.parent.parent
else:
if x == x.parent.right:
x = x.parent
# przypadek 2
root = LeftRotate(root, x)
x.parent.color = BLACK
# przypadek 3
x.parent.parent.color = RED
root = RightRotate(root, x.parent.parent)
Pobieranie koloru - NULL
def GetColor(node):
if node!=None:
return node.color
else:
return BLACK
Usuwanie węzła
NULL
NULL
Przenosimy nadmiarowy kolor
czarny na syna usuwanego wierzchołka
Problem: wierzchołek mógł nie mieć syna
Czy mogl mieć czarnego syna(ów) ?
Usuwanie węzła
•
•
Usuń węzeł podobnie jak dla zwykłego drzewa
Jeżeli usuwany wierzchołek był koloru czarnego
należy wykonac porządkowanie drzewa (naruszona
może być własność 3 lub 4):
Porządkowanie RBT po DEL - 1
2
x
A
10
1
B
brat
7
C
15
D E
F
Przypadek 1
10
2
x
Jeżeli brat jest czerwony
A
brat
7
(nowy)
1
B
C
15
E
F
D
przemaluj wierzchołki brat, ojciec(x), obróć drzewo wokół wierzchołka
ojciec(x) w kierunku syna x (tj. ) i zaktualizuj brat
Uwaga: po tym kroku brata jest czarny
Porządkowanie RBT po DEL - 2
2
x
A
7
1
B
brother
5
C
Przypadek 2
9
D E
F
7
1
A
B
5
C
Jeżeli obaj synowie brat-a są czarni
przemaluj brat-a i ustaw rozpocznij od ojciec(x)
nowe x
2
9
D E
F
Porządkowanie RBT po DEL - 3
2
x
7
1
brother
Przypadek 3
A
B
5
2
x
9
5
1
C
D E
F
A
B
brother
(nowy)
C
Jeżeli przynajmniej jeden syn brat-a jest czerwony
jeżeli dalszy syn brat-a (w kierunku ’) jest czarny
7
D
9
E
drugiego syna brat-a pomaluj na czarno, brat-a na czerwono i obróć drzewo
wokoł wierzchołka brat w kierunku ’, zaktualizuj brat
Uwaga: po kroku trzecim dalszy syn brata bedzie czerwony
F
Porządkowanie RBT po DEL - 4
2
x
5
1
brother
5
A
B
3
C
Przypadek 4
7
D E
F
2
x
A
7
3
1
E
B
C
D
STOP: np x = proot
Jeżeli dalszy syn brat-a jest czerwony
przemaluj brat-a na kolor taki jak ojciec(x),
wierzchołki ojciec(x) oraz dalszego syna brat-a (w kierunku ’) na czarno,
obróć drzewo wokół ojca w kierunku x (tj. )
F
Porządkowanie drzewa po DEL
Dopóki ojciec(x) jest czarny i nie doszliśmy do korzenia
if brat jest czerwony :
1.
przemaluj wierzchołki brat, ojciec(x), obróć drzewo wokół
wierzchołka ojciec(x) w kierunku syna x (tj. ) i zaktualizuj brat
elif obaj synowie brat-a są czarni :
2.
przemaluj brat-a i ustaw rozpocznij od ojciec(x)
else :
3.
if dalszy syn brat-a (w kierunku ’) jest czarny :
drugiego syna brat-a pomaluj na czarno, brat-a na czerwono
obróć drzewo wokoł wierzchołka brat w kierunku ’, zaktualizuj brat
4.
przemaluj brat-a na kolor taki jak ojciec(x),
przemaluj wierzchołki ojciec(x) oraz dalszego syna brat-a (w
kierunku ’) na czarno,
obróć drzewo wokół ojca w kierunku x (tj. )
zakończ porządkowanie (np. podstaw x = proot)
Uwagi implementacyjne
Aby uniknąć w kodzie sprawdzania warunków czy syn(owie) !=
None przed pobraniem koloru / sprawdzeniem typu /
odwolaniem sie do ojca można:
dodać funkcje realizujące odpowiednie testy (por. GetColor)
dodać wartownika np NIL – specjalny węzeł który ma
wszystkie wskaźniki == None i na który pokazują wszystkie
wskaźniki dawniej równe None
Przypisanie: son.parent = todel.parent można wykonać
bezwarunkowo (syn moze być ew. wartownikiem, ale istnieje)
- w takim przypadku NIL.parent == todel.parent.
Usuwanie węzła – implem.
def RBTDelete(root, p):
if p.left==None or p.right==None: todel = p
else todel = BSTSuccesor(p)
if todel.left != NIL: son = todel.left
else: son = todel.right
son.parent = todel.parent
if todel.parent==None:
root = son
elif todel == todel.parent.left:
todel.parent.left = son
else:
todel.parent.right = son
if todel != p:
p.key = todel.key
p.data = todel.data
if todel.color == BLACK:
root = RBTDeleteFix(root, son)
return root
DEL - Porządkowanie węzłów
def RBTDeleteFix (root, x):
while x != root and x.color == BLACK:
if x == x.parent.left:
# porządkowanie dla lewego węzła
else:
# porządkowanie dla prawego węzła
x.color = BLACK
DEL - Porządkowanie lewego węzła
brother = x.parent.right
if brother.color == RED:
brother.color = BLACK
# przypadek 1
x.parent.color = RED
root = LeftRotate (root, x.parent)
brother = x.parent.right
elif (brother.left.color == BLACK and\
brother.right.color = = BLACK:
brother.color = RED
# przypadek 2
x = x.parent
else:
...
Porządkowanie lewego węzła cd
else:
if brother.right.color == BLACK:
brother.left.color = BLACK
#przypadek 3
brother.color = RED
root = RightRotate(root, brother)
brother = x.parent.right
brother.color = x.parent.color
#przypadek 4
x.parent.color = BLACK
brother.right.color = BLACK
root = LeftRotate (root, x.parent)
x = root
RBT wzbogacone o statystyki poz.
Aktualizacja rozmiarów:
def LeftRotate (root, x):
.....
y.size = getsize(y);
x.size = getsize(x.left) + getsize(x.right) +1
return root
y
93
19
x 42
19
T=RightRotate(T,y)
x
42
11
93
12
7
T=LeftRotate(T,x)
6
y
4
6
4
7
B-drzewo
n.keys[0]
n.keys[1]
.M.
.D.H.
n.sons[0] n.sons[1] n.sons[2]
B C
F G
J K L
.Q.T.X.
N P
R S
V W
•Wszystkie klucze dla i-tego syna jego potomków są wieksze
lib równe od i-tego klucza i mniejsze lub równe od i+1
•Węzeł o i synach ma i-1 kluczy
•Wezły różne od korzenia zawierają co najmniej T-1 kluczy
(stąd węzły wewnętrzne maja conajmniej t synów)
•Węzły zawierają conajwyżej 2T-1 kluczy (stąd węzły
wewnętrzne maja conajwyżej 2T synów -> węzły pełne)
Y Z
Minimalne B-drzewo o h=3
root
1
1
T-1
T-1
T-1
T
T-1
T
T-1
T-1
T-1
T
T-1
T-1
T
T
T-1 T-1 T-1
Dla T = 2 otrzymujemy tzw. 2-3-4 drzewo
2
T-1
2t
T
T-1
2t2
Właściwości B-drzewa





B-drzewo jest zrównoważone
Zmienna liczba kluczy i synów
Wszystkie liście są na tej samej głębokości
Mała głębokość drzewa
Zaprojektowane do minimalizacja dostepów np. do
dysku – korzeń wczytuje się pamięci od razu
Definicja węzła
T = 5
class BNODE:
isLeaf=true
cntKey=0
keys = Array(2*T-1, None)
sons = Array(2*T, None)
#pozycja na dysku biezacego wezla
thisNodeDiscPos = None
#pozycje na dysku dla danych odpowiadających
#poszczegolnym kluczom
dataDiscPos = Array(2*T-1, None)
def Array(size, initVal=None):
return map(lambda x: initVal, range(0,size))
class DISCPOS:...
FunkcjePomocnicze
def LoadNode(nodeDiscPos)
# alokacja w pamięci i odczyt
def WriteNodeToDisc(node)
# zapis na dysk pod pode. thisNodeDiscPos
AllocateNode()
# alokacja w pamięci i na dysku,
# zapis struktury na dysk
p = BNODE()
#p.isLeaf = true, p.cntKey = 0
p.thisNodeDiscPos = AllocateSpaceOnDisc()
WriteNodeToDisc(p)
return p
Wyszukiwanie w B-drzewie
BTreeFind(p,k):
if p_zawiera_szukany_klucz k:
return p
elif p_jest_liściem:
return None
else:
# p nie jest liściem i nie zawiera k
s = wytypuj_poddrzewo_p_które_może_zwierać_k
ptmp = LoadNode(s)
ret = BTreeFind(ptmp,k)
#zadbaj o zwolnienie ptmp jeśli ret!=ptmp
return ret
Wyszukiwanie w B-drzewie
BTreeFind(p,k):
for i in range(0, p.cntKey):
if k<=p.keys[i]:break
if p.keys[i] == k:
return p
if p.isLeaf:
return None
ptmp = LoadNode(p.sons[i])
ret = BTreeFind(ptmp, k)
return ret
Rozbijanie węzła T = 4
p
keys[i-1] keys[i]
N.W
sons[i]
w .P.Q.R.S.T.U.V.
w
keys[i]
keys[i-1]
keys[i+]
N.S.W
p
sons[i]
sons[i+1]
.P.Q.R.
.T.U.V.
y
Rozbijanie korzenia T=4
w .P.Q.R.S.T.U.V.
root
w
keys[0]
p
.S.
sons[0]
sons[1]
.P.Q.R.
.T.U.V.
y
Rozbijanie węzła w B-drzewie
5.
rozbijamy pełen węzeł w będący i-tym synem węzła p
środkowy z 2*T-1 kluczy w w wstawiamy do węzła p
(przed element na pozycji i)
wskaźnik na nowy węzeł z wstawiamy do węzła p
(przed element na pozycji i)
T-1 kluczy z w przepisujemy do z
T wskaźników z w przepisujemy do z
6.
zwracamy nowy węzeł (odbiorca powinien go zwolnić)
1.
2.
3.
4.
Rozbijanie
węzła
w
B-drzewie
BTreeSplit(p, i, w):
#Zalozenie: p!=w jeśli mamy rozbic korzeń najpierw
#należy dodac nowy wezel(powyzej korzenia)
z = AllocateNode()
z.isLeaf = w.isLeaf
z.cntKeys, w.cntKeys = T-1, T-1
for j in range(p.cntKey-1,i,-1):
p.keys[j]=p.keys[j-1]
#p.data[j]=p.data[j-1]
for j in range(p.cntKey, i,-1):
p.sons[j]=p.sons[j-1]
p.keys[i] = w.keys[T-1] #p.data[i]=w.data[T-1]
p.sons[i] = z
p.cntSons = p.cntSons +1
for j in range(0, T-1):
z.keys[j] = w.keys[T+j] #z.data[j]=w.data[T-1+j]
for i in range(0,T):
z.sons[j] = w.sons[T+j]
WriteNodeToDisc(p)
WriteNodeToDisc(w)
WriteNodeToDisc(z)
return z
.G.M.P.X.
T=3
ACDE
JK
+B
NO
RSTUV
YZ
.G.M.P.X.
ABCDE
JK
NO
RSTUV
YZ
+Q
.G.M.P.T.X.
ABCDE
JK
NO
QRS
UV
YZ
.G.M.P.T.X.
T=3
ABCDE
JK
NO
+L
QRS
UV
YZ
.P.
.G.M.
ABCDE
JKL
.T.X.
NO
+F
QRS
UV
YZ
.P.
.C.G.M.
AB
DEF
JKL
.T.X.
NO
QRS
UV
YZ
Wstawianie klucza do B-drzewa
(1) if korzeń jest pełny:
dodajemy nowy korzeń i rozbijamy dotychczasowy na dwa
(2) Wybieramy syna p mogącego zawierać nowy klucz k
if p jest pełen:
rozbijamy p na dwa p i q
Wybieramy odpowiedni z wezłów p lub q
if wybrany wezeł jest liściem:
dodajemy do niego klucz k
else:
dla wybranego węzła wołamy rekurencyjnie (2)
Wstawianie klucza do B-drzewa
def BTreeInsert(root, key, data):
if root.cntSons == 2*T-1:
BNODE * s = AllocateNode()
s.sons[0] = r
s.cntKeys = 0
s.isLeaf = false
BTreeSplit(s, 0, root)
root = s
BTTreeInsert_NonFul(root, k, data)
return root
Wstawianie klucza do B-drzewa
def BTreeInsert_NonFull(x, k, data):
if (x.isLeaf):
BTreeInsert_ToLeaf(x, k, data)
else:
BTreeInsert_ToNonLeaf(x, k, data)
def BTreeInsert_ToNonLeaf(x, k, data) :
for i in range(x.cntSons-1,-1,-1):
if k>=x.keys[i]: break
i=i+1
ptmp = LoadNode(x->sons[i])
if ptmp.cntSons == 2*T-1:
q = BTreeSplit(x, i, ptmp)
if k>x.keys[i]:
ptmp=q
BTreeInsert_NonFull(ptmp, k, data)
Wstawianie klucza do B-drzewa
def BTreeInsert_ToLeaf(x, k, data):
for i in range(x.cntSons-1, -1, -1):
if k<x.keys[i]:
x.key[i+1] = x.key[i]
else:
break
x.key[i+1] = k
x.data[i+1] = AllocateDataOnDisc(data)
x.cntKeys = x.cntKeys +1
WriteNode(x)
T=3
AB
.P.
.C.G.M.
DEF
JKL
.T.X.
NO
-F
QRS
UV
YZ
.P.
.C.G.M.
AB
DE
JKL
.T.X.
NO
-M
QRS
UV
YZ
.P.
.C.G.L.
AB
DE
JK
.T.X.
NO
QRS
UV
YZ
T=3
AB
DE
.P.
.C.G.L.
JK
.T.X.
NO
QRS
UV
YZ
.L.
-S
.C.G.
AB
DE
JK
.P.T.X.
NO
QRS
UV
YZ
-S
.L.
.C.G.
AB
DE
JK
.P.T.X.
NO
QR
UV
YZ
.P.
T=3
AB
.C.G.L.
DE
JK
.T.X.
NO
-G
QRS
UV
YZ
.P.
.C.L.
AB
DEJK
.T.X.
NO
QRS
UV
YZ
-D
.C.L.P.T.X.
AB
EJK
NO
QRS
UV
YZ
Usuwanie klucza z B-drzewa
Nie wchodzimy na węzły minimalne
if x jest liściem i zawiera klucz k:
usuń klucz k z węzła x
if x jest nie jest liściem i zawiera klucz k:
if syn y porzedzający k ma conajmniej T kluczy:
w poddrzewie y wyznacz k1 = poprzednik k
rekurenyjnie usuń k1 i zastąp k przez k1
elif syn z następujący po k ma conajmniej T kluczy:
w poddrzewie z wyznacz k1 = nastepnik k
rekurencyjnie usuń k1 i zastąp k przez k1
else:
przenieś k i wszystko z węzła z do węzła y
(z węzła x usuwamy k oraz wskaźnik do z)
usuń z z dysku i zdealokuj pamięć
rekurencyjnie usuń k z węzła y
Usuwanie klucza z B-drzewa
if x jest liściem i nie zawiera klucza k:
koniec
if x nie jest liściem i nie zawiera klucza k:
p = wyznacz poddrzewo które może zwierać k
if p zawiera T-1 kluczy:
if jeden z braci p ma T kluczy:
przesuń odpowiedni klucz z x do p
przesuń odpowiedni klucz z brata p do x
przesuń odpowiedni wskaźnik z brata p do p
else:
połącz p z jednym z braci
przenieś odpowiedni klucz z x do powstałego węzła
zwolnij pusty węzeł
zwolnij miejsce na dysku
kontynuuj dla p
Usuwanie klucza z B-drzewa
Uwagi:
– większość węzłów znajduje sie w liściach stąd zwykle
usunięcie będzie polegało na pojedynczym zejściu w dół
drzewa
– w przypadku wezłów wewnętrznych konieczne może być
rekurencyjne usuwanie co wymagac może przejścia w dół
i powrotu