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 ReportTranscript 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