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