algorytmy i struktury danych
DESCRIPTION
Algorytmy i struktury danych. R ównoważenie drzew Drzewa czerwono-czarne Drzewa kontekstowe B-drzewa. Drzew a zrównoważone. Czas operacji na BST jest proporcjonalny do wysokości drzewa - PowerPoint PPT PresentationTRANSCRIPT
Algorytmy i struktury Algorytmy i struktury danychdanych
Równoważenie drzew
Drzewa czerwono-czarne
Drzewa kontekstowe
B-drzewa
DrzewDrzewa zrównoważonea 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.
PrzykPrzykłłady drzewady drzew zrzrównoważonychównoważonych Drzewa AVL Drzewa czerwono czarne B-drzewa
DrzewoDrzewo czerwono-czarneczerwono-czarne1) 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ść)
NULLNULL
NULLNULLNULLNULL
NULLNULLNULL
NULLNULL
NULLNULL
NULLNULL
NULLNULL
NULLNULLNULL
NULLNULL
DrzewoDrzewo czerwono-czarne (RBT) czerwono-czarne (RBT)
Wysokość drzewa RBT 2lg(n+1)
30
4125
2815 35
3727 29188
10420 26
1
45
33
36 40
Równowaga w RBTRównowaga w RBTKaż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 RBTWł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łaDefinicja węzłaclass NODE :
data = Nonecolor = BLACKleft = Noneright = Noneparent = none
Operacja rotacjiOperacja rotacji
y
x
A B
C
x
yA
B CLeftRotate(T,x)
RightRotate(T,y)
LeftRotateLeftRotate
def LeftRotate(root, x): # zmiana na righty = x.right # x.leftif (y==None): returnx.right = y.left # x.rightif (y.left!=None): y.left.parent = x # y.righty.parent = x.parentif x.parent==None: root = yelif x==x.parent.left: x.parent.left = yelse: x.parent.right = yy.left = x # y.rightx.parent = yreturn root
y
x
A B
C
x
yA
B C
Wstawianie węzłaWstawianie węzła
Ale może naruszyć zasadę 3
Narusza długość czarnych ścieżek
Nie narusza długości czarnych ścieżek
Wstawianie węzłaWstawianie 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 Porządkowanie RBTRBT po INS - 1 po INS - 1
4
5
6 10
25
4
5
8 25
Przypadek 1
x
stryj
15
1 8
15
6 10
1
20
20
nowy x
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)
Porządkowanie Porządkowanie RBTRBT po INS - 2 po INS - 215
4
5
6 10
1
20
825
15
4
5
6
10
1
148
15
Przypadek 2
x
stryj
nowy x
stryj
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
Porządkowanie Porządkowanie RBTRBT po INS - 3 po INS - 3
Przypadek 3
112
4
5 81
14
7
15
11
2
4
5
8
1
147
15x
stryj
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.
x
Porządkowanie drzewaPorządkowanie drzewa p po INSo INS Z: korzeń drzewa jest czarny (czemu nie ?)• Dopóki ojciec(x) jest czerwony i nie doszliśmy do
korzeniaJeż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 razieJeż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.Wstawianie węzła – implem.def RBTInsert(root, x):
Insert(root, x)x.color = RED# zalozenie: root.color == BLACKwhile 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 prawejroot.color = BLACKreturn root
Porządkowanie dla ojca po lewejPorządkowanie dla ojca po lewej# Porządkowanie dla ojca po lewejuncle = x.parent.parent.right
if GetColor(uncle) == RED : x.parent.color = BLACK # przypadek 1 uncle.color = BLACK x.parent.parent = RED x = x.parent.parentelse: 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 - NULLPobieranie koloru - NULLdef GetColor(node):
if node!=None: return node.colorelse: return BLACK
Usuwanie węzłaUsuwanie 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łaUsuwanie 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 Porządkowanie RBTRBT po DEL - 1 po DEL - 1
1
Przypadek 1
x2
10 brat
A BA
C D E F
1x
2
7
10
15brat (nowy)
A BA C DE F
7 15
Jeżeli brat jest czerwony
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 Porządkowanie RBTRBT po DEL - 2 po DEL - 2
Przypadek 2
1x
2
7
BA
C D E F 1
nowe x2
7
BA
C D E F
brother
5 9
5 9
Jeżeli obaj synowie brat-a są czarni
przemaluj brat-a i ustaw rozpocznij od ojciec(x)
Porządkowanie Porządkowanie RBTRBT po DEL - 3 po DEL - 3
Przypadek 31
x
2
7
BA
C D E F1 brother
(nowy)
2
BA C
D
E F
brother
5 9
5
9
7
x
Jeżeli przynajmniej jeden syn brat-a jest czerwony
jeżeli dalszy syn brat-a (w kierunku ’) jest czarny
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
Porządkowanie Porządkowanie RBTRBT po DEL - 4 po DEL - 4
Przypadek 4
1x
2
5
BA
C D E F
brother
3 7
1x
2
5
BA C D
E F3
7
STOP: np x = proot
Jeżeli dalszy syn brat-a jest czerwonyprzemaluj 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. )
Porządkowanie drzewa po DELPorządkowanie drzewa po DELDopó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 Uwagi impimpllementacementacyjneyjneAby 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.Usuwanie węzła – implem.def RBTDelete(root, p):
if p.left==None or p.right==None: todel = pelse todel = BSTSuccesor(p)if todel.left != NIL: son = todel.leftelse: son = todel.rightson.parent = todel.parentif todel.parent==None: root = sonelif todel == todel.parent.left: todel.parent.left = sonelse: todel.parent.right = sonif todel != p: p.key = todel.key p.data = todel.dataif todel.color == BLACK: root = RBTDeleteFix(root, son)return root
DEL - Porządkowanie węzłówDEL - Porządkowanie węzłówdef 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łax.color = BLACK
DEL - Porządkowanie lewego węzłaDEL - 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 cdPorządkowanie lewego węzła cdelse:
if brother.right.color == BLACK: brother.left.color = BLACK #przypadek 3 brother.color = RED root = RightRotate(root, brother) brother = x.parent.rightbrother.color = x.parent.color #przypadek 4x.parent.color = BLACKbrother.right.color = BLACKroot = LeftRotate (root, x.parent)x = root
RBT wzbogacone oRBT wzbogacone o statystyk statystykii poz poz..
Aktualizacja rozmiarów:def LeftRotate (root, x):
.....
y.size = getsize(y);
x.size = getsize(x.left) + getsize(x.right) +1return root
4211
9319
6
7
4
x
y 4219
9312
6
74
xy
T=LeftRotate(T,x)
T=RightRotate(T,y)
B-drzewoB-drzewo. M .
. D . H . . Q . T . X .
B C F G J K L N P R S V W Y Z
•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)
n.keys[0]n.keys[1]
n.sons[1]n.sons[0] n.sons[2]
Minimalne B-drzewo o h=3Minimalne B-drzewo o h=3
1
T - 1
T
T - 1
TT - 1
T
T - 1
T
T - 1
T
T - 1
TT - 1T - 1 T - 1T - 1 T - 1 T - 1 T - 1 T - 1
1
2
2t
2t2
root
Dla T = 2 otrzymujemy tzw. 2-3-4 drzewo
Właściwości B-drzewaWł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łaDefinicja węzłaT = 5
class BNODE:
isLeaf=truecntKey=0keys = Array(2*T-1, None)
sons = Array(2*T, None)
#pozycja na dysku biezacego wezlathisNodeDiscPos = None
#pozycje na dysku dla danych odpowiadających#poszczegolnym kluczomdataDiscPos = Array(2*T-1, None)
def Array(size, initVal=None):
return map(lambda x: initVal, range(0,size))
class DISCPOS:...
FunkcjePomocniczeFunkcjePomocniczedef 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-drzewieWyszukiwanie w B-drzewieBTreeFind(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!=ptmpreturn ret
Wyszukiwanie w B-drzewieWyszukiwanie w B-drzewieBTreeFind(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 = 4Rozbijanie węzła T = 4
N . W
. P . Q . R . S . T . U . V .
keys[i-1] keys[i]
sons[i]
N . S . W
. T . U . V .
keys[i-1] keys[i+]
sons[i+1]. P . Q . R .
keys[i]
sons[i]
p
w
w
p
y
Rozbijanie korzenia T=4Rozbijanie korzenia T=4
. P . Q . R . S . T . U . V .
root
. S .
. T . U . V .
keys[0]
sons[1]. P . Q . R .
sons[0]
p
w y
w
Rozbijanie węzła w B-drzewieRozbijanie węzła w B-drzewie1. rozbijamy pełen węzeł w będący i-tym synem węzła p
2. środkowy z 2*T-1 kluczy w w wstawiamy do węzła p (przed element na pozycji i)
3. wskaźnik na nowy węzeł z wstawiamy do węzła p (przed element na pozycji i)
4. T-1 kluczy z w przepisujemy do z
5. T wskaźników z w przepisujemy do z
6. zwracamy nowy węzeł (odbiorca powinien go zwolnić)
Rozbijanie węzła w B-drzewieRozbijanie węzła w B-drzewieBTreeSplit(p, i, w):
#Zalozenie: p!=w jeśli mamy rozbic korzeń najpierw #należy dodac nowy wezel(powyzej korzenia)z = AllocateNode()z.isLeaf = w.isLeafz.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] = zp.cntSons = p.cntSons +1for 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
T=3 T=3 . G . M . P . X .
A C D E J K N O R S T U V Y Z
+B . G . M . P . X .
A B C D E J K N O R S T U V Y Z
+Q
. G . M . P . T . X .
A B C D E J K N O Y ZQ R S U V
. T . X .
T=3 T=3 . G . M . P . T . X .
A B C D E J K N O Y ZQ R S U V
+L
. G . M .
A B C D E J K L N O Y ZQ R S U V
. P .
. T . X .
+F
. C . G . M .
J K L N O Y ZQ R S U V
. P .
A B D E F
Wstawianie klucza do B-drzewaWstawianie 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-drzewaWstawianie klucza do B-drzewadef BTreeInsert(root, key, data):
if root.cntSons == 2*T-1:BNODE * s = AllocateNode() s.sons[0] = rs.cntKeys = 0s.isLeaf = falseBTreeSplit(s, 0, root)root = s
BTTreeInsert_NonFul(root, k, data)return root
Wstawianie klucza do B-drzewaWstawianie klucza do B-drzewadef 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]: breaki=i+1ptmp = LoadNode(x->sons[i])
if ptmp.cntSons == 2*T-1:q = BTreeSplit(x, i, ptmp)if k>x.keys[i]:
ptmp=qBTreeInsert_NonFull(ptmp, k, data)
Wstawianie klucza do B-drzewaWstawianie klucza do B-drzewadef 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: breakx.key[i+1] = kx.data[i+1] = AllocateDataOnDisc(data)x.cntKeys = x.cntKeys +1 WriteNode(x)
T=3 T=3
-F
. T . X .. C . G . M .
J K L N O Y ZQ R S U V
. P .
A B D E F
. T . X .. C . G . M .
J K L N O Y ZQ R S U V
. P .
A B D E
-M
. T . X .. C . G . L .
J K N O Y ZQ R S U V
. P .
A B D E
. L .
T=3 T=3
-S
. T . X .. C . G . L .
J K N O Y ZQ R S U V
. P .
A B D E
. P . T . X .. C . G .
J K N O Y ZQ R S U VA B D E
. L .
. P . T . X .. C . G .
J K N O Y ZQ R U VA B D E
-S
. P .
T=3 T=3
-G
. T . X .. C . G . L .
J K N O Y ZQ R S U V
. P .
A B D E
. T . X .. C . L .
N O Y ZQ R S U VA B D E J K
-D. C . L . P . T . X .
N O Y ZQ R S U VA B E J K
Usuwanie klucza z B-drzewaUsuwanie klucza z B-drzewaNie 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 krekurenyjnie 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 krekurencyjnie 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-drzewaUsuwanie klucza z B-drzewaif 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-drzewaUsuwanie klucza z B-drzewaUwagi:
– 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