550 likes | 751 Views
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
E N D
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 drzewzrównoważonych • Drzewa AVL • Drzewa czerwono czarne • B-drzewa
Drzewoczerwono-czarne • Każdy węzeł jest czerwony lub czarny • Każdy NULL jest czarny • Jeżeli węzeł jest czerwony to obaj jego synowie są czarni • Każda ścieżka z ustalonego węzła do liścia-NULL ma tyle samo czarnych węzłów (czarna wysokość)
Drzewo czerwono-czarne (RBT) 30 • Wysokość drzewa RBT 2lg(n+1) 25 41 15 28 35 45 33 37 8 18 27 29 NULL NULL NULL 4 10 36 40 NULL NULL NULL 26 NULL 20 NULL NULL NULL 1 NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL 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 = Nonecolor = BLACKleft = Noneright = Noneparent = none
Operacja rotacji y x x y C A C B A B RightRotate(T,y) LeftRotate(T,x)
LeftRotate y x x y C A C B A B 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
Wstawianie węzła Nie narusza długości czarnych ścieżek Narusza długość 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 25 1 Przypadek 1 15 6 10 stryj 20 4 5 x nowy x 8 25 1 6 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)
Porządkowanie RBT po INS - 2 15 stryj 20 15 4 stryj 25 14 x 8 nowy x 8 1 15 4 10 6 10 Przypadek 2 6 1 5 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
Porządkowanie RBT po INS - 3 11 stryj 14 7 x 15 2 8 7 x 11 14 5 2 1 8 5 15 Przypadek 3 4 1 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.
Porządkowanie drzewa po INS Z: korzeń drzewa jest czarny(czemunie ?) • Dopóki ojciec(x) jest czerwony i nie doszliśmy do korzenia Jeżeli stryj(x) jest czerwony • 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 ) • Przyjmij x = ojciec(x) obróć drzewo w przeciwną tj ’ względem x (nowego) • 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 == BLACKwhilex != root and x.parent.color==RED:if x.parent == x.parent.parent.left: #Porządkowanie dla ojca po lewejelse: #Porządkowanie dla ojca po prawejroot.color = BLACKreturn 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 1uncle.color = BLACKx.parent.parent = REDx = x.parent.parentelse:if x == x.parent.right:x = x.parent# przypadek 2root = LeftRotate(root, x)x.parent.color = BLACK# przypadek 3x.parent.parent.color = RED root = RightRotate(root, x.parent.parent)
Pobieranie koloru - NULL def GetColor(node):if node!=None:return node.colorelse: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 brat 10 1 B A A 7 15 D C E F Przypadek 1 10 2 15 brat (nowy) x 7 1 E F 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 B C A A D
Porządkowanie RBT po DEL - 2 E E C C D F F D 2 x brother 7 1 A B Przypadek 2 5 9 nowe x 2 7 1 A B 5 9 Jeżeli obaj synowie brat-asą czarni przemaluj brat-a i ustaw rozpocznij od ojciec(x)
Porządkowanie RBT po DEL - 3 C E E F D F 2 x brother 7 1 Przypadek 3 2 A B x 5 9 brother (nowy) 5 1 A C B 7 D 9 Jeżeli przynajmniej jeden syn brat-ajest 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 RBT po DEL - 4 E E C C D F F D 2 x brother 5 1 5 A B Przypadek 4 3 7 2 7 x 3 1 A B STOP: np x = proot Jeżeli dalszy syn brat-ajest 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. )
Porządkowanie drzewa po DEL Dopóki ojciec(x) jest czarny i nie doszliśmy do korzenia if brat jest czerwony : • 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-asą czarni : • przemaluj brat-a i ustaw rozpocznij od ojciec(x) else : • 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 • 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==Noneorp.right==None:todel = p else todel = BSTSuccesor(p) if todel.left != NIL: son = todel.leftelse: son = todel.rightson.parent = todel.parent if todel.parent==None: root = son elif todel == todel.parent.left: todel.parent.left = sonelse: todel.parent.right = son if todel != p: p.key = todel.key p.data = todel.dataif todel.color ==BLACK: root = RBTDeleteFix(root, son)return root
DEL - Porządkowanie węzłów def RBTDeleteFix (root, x):whilex != 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ła brother = x.parent.right if brother.color == RED: brother.color = BLACK# przypadek 1x.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 2x = 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.rightbrother.color = x.parent.color#przypadek 4x.parent.color = BLACKbrother.right.color = BLACKroot = 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) +1return root 93 19 y 42 19 x y T=RightRotate(T,y) x 42 11 7 93 12 6 T=LeftRotate(T,x) 4 6 7 4
B-drzewo . M . n.keys[1] n.keys[0] . D . H . . Q . T . X . n.sons[0] n.sons[2] n.sons[1] 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)
Minimalne B-drzewo o h=3 T - 1 T - 1 T - 1 T - 1 T - 1 T - 1 T T T T T T root 1 2 2t 2t2 1 T - 1 T - 1 T - 1 T - 1 T - 1 T - 1 T - 1 T - 1 Dla T = 2 otrzymujemy tzw. 2-3-4 drzewo
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=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:...
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 keys[i-1] keys[i] p N . W sons[i] w . P . Q . R . S . T . U . V . keys[i] keys[i-1] keys[i+] p N . S . W sons[i] sons[i+1] w y . P . Q . R . . T . U . V .
Rozbijanie korzenia T=4 w . P . Q . R . S . T . U . V . root keys[0] p . S . sons[0] sons[1] w y . P . Q . R . . T . U . V .
Rozbijanie węzła w B-drzewie • 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 • zwracamy nowy węzeł (odbiorca powinien go zwolnić)
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.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 . 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 Q R S U V Y Z
T=3 . G . M . P . T . X . A B C D E J K N O Q R S U V Y Z +L . P . . G . M . . T . X . A B C D E J K L N O Q R S U V Y Z +F . P . . C . G . M . . T . X . A B D E F J K L N O Q R S U V Y Z
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 = falseBTreeSplit(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]: 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-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: breakx.key[i+1] = kx.data[i+1] = AllocateDataOnDisc(data)x.cntKeys = x.cntKeys +1 WriteNode(x)
T=3 . P . . C . G . M . . T . X . A B D E F J K L N O Q R S U V Y Z -F . P . . C . G . M . . T . X . A B D E J K L N O Q R S U V Y Z -M . P . . C . G . L . . T . X . A B D E J K N O Q R S U V Y Z