810 likes | 924 Views
Søgetræer. Plan. Binære søgetræer Definition Operationer Balancerede binære søgetræer AVL-træer Rød - sort -træer (AA-træer) • B-træer. Eksempel: Søgning efter N . A B E F G H I L M N P R S X L M N P R S X L M N N.
E N D
Plan • Binære søgetræer Definition Operationer • Balancerede binære søgetræer AVL-træer Rød-sort-træer (AA-træer) • • B-træer
Eksempel: Søgning efter N. A B E F G H I L M N P R S X L M N P R S X L M N N Binær søgningi et sorteret array • Metode: • Opdel arrayet i to (næsten) lige store dele. • Afgør i hvilken af de to dele, nøglen skal findes. • Fortsæt søgningen i denne del på samme måde.
I P E M S A G X N L R B F H Binært søgetræ • Binær søgning kan beskrives ved et binært søgetræ: • Et binært søgetræ, er et binært træ, hvor der for enhver knude X gælder, at alle knuder i dets venstre undertræ er mindre end X, og alle knuder i dets højre undertræ er større end X.
public class BinarySearchTree implements SearchTree { public Comparable find(Comparable x); public void insert(Comparable x); public void remove(Comparable x); public isEmpty(); public makeEmpty(); public printSorted(); public Comparable findMin(); public Comparable findMax(); } Binært søgetræ som abstrakt datatype
class BinaryNode { BinaryNode(Comparable e, BinaryNode lt, BinaryNode rt) { element = e; left = lt; right = rt; } Comparable element; BinaryNode left, right; } public class BinarySearchTree { ... BinaryNode root; } Intern repræsentation
public Comparable find(Comparable x) { return find(x, root).element; } Comparable find(Comparable x, BinaryNode t) { while (t != null) { if (x.compareTo(t.element) < 0) t = t.left; else if (x.compareTo(t.element) > 0) t = t.right; else return t; } throw new ItemNotFoundException(x.toString()); } Søgning
7 7 9 2 9 2 1 5 1 5 3 3 6 Indsættelse Indsættelse af 6 • Der foretages en mislykket søgning, og den nye knude indsættes der, hvor søgningen stopper.
public void insert(Comparable x) { root = insert(x, root); } BinaryNode insert(Comparable x, BinaryNode t) { if (t == null) t = new BinaryNode(x, null, null); else if (x.compareTo(t.element) < 0) t = insert(x, t.left); else if (x.compareTo(t.element) > 0) t = insert(x, t.right); else throw new DuplicateItemException(x.toString()); return t; } Indsættelse
7 2 9 18 15 15 18 1 1 5 5 2 9 11 3 3 11 Sletning Sletning af roden • Erstat roden med den knude, der er mindst i rodens højre undertræ. • Denne knude befinder sig længst til venstre i rodens højre undertræ. • Hvis det højre undertræ er tomt, fjernes roden blot fra træet.
public void remove(Comparable x) { root = remove(x, root); } BinayNode remove(Comparable x, BinaryNode t) { if (t == null) throw new ItemNotFoundException(x.toString()); if (x.compareTo(t.element) < 0) t.left = remove(x, t.left); else if (x.compareTo(t.element) > 0) t.right = remove(x, t.right); else if (t.right != null) { // x found t.element = findMin(t.right).element; t.right = removeMin(t.right); } else t = t.left; return t; } Sletning
t BinaryNode findMin(BinaryNode t) { if (t == null) throw new ItemNotFoundException(); while (t.left != null) t = t.left; return t; } t.left t.right BinaryNode removeMin(BinaryNode t) { if (t == null) throw new ItemNotFoundException(); if (t.left == null) return t.right; t.left = removeMin(t.left); return t; }
Alternativ implementering af sletning BinaryNode remove(Comparable x, BinaryNode t) { if (t == null) throw new IntemNotFoundException(); lastNode = t; if (x.compareTo(t.element) < 0) t.left = remove(x, t.left); else { deletedNode = t; t.right = remove(x, t.right); } if (t == lastNode) { if (t == null || x.compareTo(t.element) != 0) throw new IntemNotFoundException(x.toString()); deletedNode.element = t.element; deletedNode = null; t = t.right; } return t; }
7 2 15 18 void printSorted(BinaryNode t) { if (t != null) { printSorted(t.left); System.out.println(t.element); printSorted(t.right); } } 1 5 9 3 11 Inorder-traversering af træet Udskrivning af elementerne i sorteret rækkefølge
BinaryNode FindKth(int k, BinaryNode t) { if (t == null) throw new ItemNotFoundException(); int leftSize = t.left != null ? t.left.size : 0; if (k == leftSize + 1) return t; if (k <= leftSize) return findKth(k, t.left); return findKth(k - (leftSize + 1), t.right); } t leftSize Find det k’te mindste element Vedligehold for enhver knude t en variabel, size, der angiver antallet af knuder i det træ, der har t som rod.
1 3 5 9 Kompleksitet Kompleksiteten af søgning, indsættelse og sletning afhænger af søgetræets udseende. I alle tre tilfælde er tiden proportional med længden af den anvendte søgevej. I bedste fald, nemlig når træet er fuldt, udføres cirka log2N sammenligninger. I værste fald, nemlig når træet er en lineær liste, udføres N sammenligninger.
Det gennemsnitlige tilfælde Hvis hvert element indsættes i tilfældig rækkefølge i et fra starten tomt binært søgetræ, vil længden af søgevejen i det frembragte træ i gennemsnit være 1.38 log2N. I praksis er udførelsestiden for de tre grundoperationer O(logN) for tilfældigt input. Det er dog endnu ikke blevet påvist analytisk.
Balancerede søgetræer • Balancering er en teknik, der garanterer, at de værste tilfælde ved søgning ikke forekommer. • Ideen er at omorganisere træet under indsættelse og sletning, så det bliver fuldt (eller næsten fuldt). • Et fuldt træ siges at være i perfekt balance. For enhver knude gælder nemlig, at antallet af knuder i dens venstre undertræ er lig med antallet af knuder i dens højre undertræ. • I det følgende præsenteres en række datastrukturer, der garanterer O(logN) kompleksitet for såvel søgning, indsættelse som sletning.
BinarySearchTree buildTree(Comparable[] a) { sort(a); BinarySearchTree bst = new BinarySearchTree(); bst.root = buildTree(a, 0, a.length - 1); return bst; } Opbygning af et balanceret søgetræ ud fra en tabel af poster BinaryNode buildTree(Comparable[] a, int low, int high) { if (low > high) return null; int mid = (low + high) / 2; return new BinaryNode(a[mid], buildTree(a, low, mid - 1), buildTree(a, mid + 1, high)); }
AVL-træer(Adelson-Velskii og Landis, 1962) • Et AVL-træ er et binært søgetræ, som opfylder følgende egenskab: • For enhver knude i træet gælder, at højden af dens venstre undertræ og højden af dens højre undertræ højst afviger med 1.
15 18 (3) 8 5 9 7 (1) (2) 11 2 (0) (1) (0) (0) (0) Eksempel på et AVL-træ • Højden af er træ er det maksimale antal af kanter fra træets rod til et af dets blade. For et tomt træ vedtages, at højden er -1.
X H H-2 H-1 SH-2 SH-1 Indsættelse i AVL-træ Indsættelse i X’s venstre undertræ kan ødelægge AVL-egenskaben. I det følgende betegner X den “dybeste” knude, som er rod i et træ, der ikke længere opfylder AVL-egenskaben.
X X X X Indsættelse i AVL-træ4 tilfælde Ved indsættelse i et af X’s undertræer er der 4 mulige tilfælde: 1. Indsættelsen sker i det venstre undertræ af X’s venstre søn. 2. Indsættelsen sker i det højre undertræ af X’s venstre søn. 3. Indsættelsen sker i det venstre undertræ af X’s højre søn. 4. Indsættelsen sker i det højre undertræ af X’s højre søn. Tilfælde 1 og 4 er symmetriske. Tilfælde 2 og 3 er symmetriske.
X k2 k1 k1 k2 C C B B A A En højrerotation genskaber balancen Tilfælde 1
k2 X k2 k1 k1 C C A A B B En højrerotation genskaber ikke balancen Tilfælde 2
k1 X k3 k3 k3 k2 k2 k1 k1 k2 D D C B C D A A A C B B Tilfælde 2(fortsat) En venstre-højre-dobbeltrotation genskaber balancen
k2 X k2 k1 k1 C C B B A A BinaryNode rotateWithLeftChild(BinaryNode k2) { BinaryNode k1 = k2.left; k2.left = k1.right; k1.right = k2; return k1; } Implementation af højrerotation
k1 X k3 k2 k3 k3 k2 k1 k1 k2 D D D C C A A A C B B B BinaryNode doubleRotateWithLeftChild(BinaryNode k3) { k3.left = rotateWithRightChild(k3.left); return rotateWithLeftChild(k3); } Implementation af dobbeltrotation
Hjælpemetode int height(BinaryNode t) { return t == null ? -1 : t.height; } Højdeinformation I klassen BinaryNode tilføjes et ekstra felt: int height. height angiver højden af det træ, der har den angivne knude som rod.
Implementering af metoden insert BinaryNode insert(Comparable x, BinaryNode t) { if (t == null) t = new BinaryNode(x, null, null); else if (x.compareTo(t.element) < 0) { t.left = insert(x, t.left); if (height(t.left) - height(t.right) == 2) if (x.compareTo(t.left.element) < 0) t = rotateWithLeftChild(t);// case 1 else t = doubleRotateWithLeftChild(t);// case 2 } else if (x.compareTo(t.element) > 0 ) { ... // case 3 or 4 } else throw new DuplicateItemException(); t.height = Math.max(height(t.left), height(t.right)) + 1; return t; }
k2 X k2 k1 k1 C C B B A A BinaryNode RotateWithLeftChild(BinaryNode k2) { BinaryNode k1 = k2.left; k2.left = k1.right; k1.right = k2; k2.height = Math.max(height(k2.left), height(k2.right)) + 1; k1.height = Math.max(height(k1.left), k2.height) + 1; return k1; } Opdatering af height
60 20 70 70 85 10 50 65 5 30 65 55 60 20 85 10 50 65 5 55 30 65 45 Eksempel på indsættelse 45 indsættes: fortsættes
60 70 20 85 10 50 65 70 5 55 30 65 45 60 50 85 20 65 55 10 30 5 65 45 Ubalance i 60: Venstrerotation ved 20: fortsættes
60 50 85 20 65 55 70 70 60 10 30 5 65 45 50 20 10 55 30 85 65 5 65 45 Højrerotation ved 60: slut
Effektiviteten af AVL-træer Højden H af et AVL-træ tilfredsstiller H < 1.44 log2(N+2) - 1.328 I et tilfældigt konstrueret træ er højden meget tæt på log2(N).
Problem Et skakbræt med 8 x 8 felter kan dækkes af 32 dominobrikker, som hver dækker 2 af brættets felter. To af brættets hjørnefelter på samme diagonal skæres væk. Kan 31 brikker nu dække brættet? Farvelægning løser problemet.
Rød-Sort-træer(Bayer, 1972) • Et Rød-Sort-træ er et binært søgetræ, som opfylder følgende 4 egenskaber: • 1. Enhver knude er farvet enten rød eller sort. • 2. Roden er sort. • 3. Hvis en knude er rød, så er dens sønner sorte. • 4. Enhver vej fra roden til en null-reference indeholder det samme antal sorte knuder.
30 70 15 85 10 20 60 50 65 80 90 5 40 55 Eksempel på et Rød-Sort-træ 0. Træet er et binært søgetræ. 1. Enhver knude er farvet enten rød eller sort. 2. Roden er sort. 3. Hvis en knude er rød, så er dens sønner sorte. (null regnes for sort) 4. Enhver vej fra rod til blad indeholder det samme antal sorte knuder. (3)
Effektiviteten af Rød-Sort-træer Højden H af et Rød-Sort-træ tilfredsstiller H ≤ 2 log2(N+1) I et tilfældigt konstrueret træ er højden meget tæt på log2(N). (faktisk 1.002 log2(N))
Indsættelse i Rød-Sort-træer En ny knude indsættes som nyt blad i træet. Hvis den nye knude farves sort, ødelægges rød-sort-egenskab 4, og det er ikke umiddelbart klart, hvorledes egenskaben kan genoprettes. Hvis den nye knude farves rød, og dens far samtidig er sort, vil ingen af rød-sort-egenskaberne blive ødelagt. Hvis dens far derimod er rød, ødelægges rød-sort-egenskab 3. Vi skal sørge for, at en ny knude indsættes som rød søn af en sort far. Hvordan? Svar: Benyt rotationer og farveskift.
P P G P S X X G G B A B C C B C A A S S X rotateWithLeftChild(G) Skift farve på P og G Behandling af 5 tilfælde X: den aktuelle røde knude. P: X’s røde far. G: X’s farfar (må nødvendigvis være sort) S: X’s farbror Tilfælde 1: X er venstre-søn til P, P er venstre-søn til G, og S er sort.
G X X S G G P S S P P X P X rotateWithLeftChild(G) A A A C C C A C B1 B2 B2 B1 B1 B2 B1 B2 G rotateWithRightChild(P) S Skift farve på X og G Tilfælde 2: X er højre-søn til P, P er venstre-søn til G, og S er sort.
G G P S P S X X C C B B A A Skift farve på P, S og G. Hvis G er rod, så farv G sort. Tilfælde 3: X er højre-søn til P, P er højre-søn til G, og S er sort. Er symmetrisk med tilfælde 1 Tilfælde 4: X er venstre-søn til P, P er højre-søn til G, og S er sort. Er symmetrisk med tilfælde 2 Tilfælde 5: Både P og S er røde.
I tilfælde 5 kan en rød-farvning af G ødelægge rød-sort-egenskab 3. Dette problem kan dog let løses ved en enkelt-rotation (tilfælde 1 og 3) eller en dobbelt-rotation (tilfælde 2 og 4). Ingen af disse rotationer giver i sig selv anledning til yderligere rotationer. Rotationerne af type 5 kræver adgang til X’s oldefar. I lærebogens algoritme vedligeholdes således følgende referencer: current: den aktuelle knude parent: den aktuelle knudes far grand: den aktuelle knudes bedstefar great: den aktuelle knudes oldefar Disse referencer kan undværes, hvis indsættelse foretages rekursivt. Dette vil blive beskrevet i det følgende.
Implementering I klassen BinaryNode tilføjes feltet int color; I klassen RedBlackTree defineres konstanterne static final int RED = 0; static final int BLACK = 1; I stedet for null-referencer benyttes objektet nullNode: static BinaryNode nullNode; static { nullNode = new BinaryNode(null); nullNode.left = nullNode.right = nullNode; nullNode.color = BLACK; }
public void insert(Comparable x) { root = insert(x, root, true); root.color = BLACK; } BinaryNode insert(Comparable x, BinaryNode t, boolean rightChild) { if (t == nullNode) { t = new BinaryNode(x, nullNode, nullNode); t.color = RED; } else { if (t.left.color == RED && t.right.color == RED) { t.color = RED; t.left.color = t.right.color = BLACK; } if (x.compareTo(t.element) < 0) { t.left = insert(x, t.left, false); if (rightChild && t.color == RED && t.left.color == RED) t = rotateWithLeftChild(t); if (t.left.color == RED && t.left.left.color == RED) { t = rotateWithLeftChild(t); t.color = BLACK; t.right.color = RED; } } else if (x.compareTo(t.element) > 0) {...} else throw new DuplicateItemException(x.toString()); } return t; }
t.right = insert(x, t.right, true); if (!rightChild && t.color == RED && t.right.color == RED) t = rotateWithRightChild(t); if (t.right.color == RED && t.right.right.color == RED) { t = rotateWithRightChild(t); t.color = BLACK; t.left.color = RED; } Behandling af de 2 symmetriske tilfælde left erstattes med right right erstattes med left rightChild erstattes med !rightChild
30 70 70 15 85 85 10 20 60 30 50 65 80 90 5 15 40 55 10 20 60 50 65 80 90 5 40 55 fortsættes 45 Eksempel på indsættelse 45 indsættes: Farveskift på 50, 40 og 55:
30 15 10 20 60 70 50 65 80 90 5 85 85 40 55 30 15 60 50 45 10 20 70 40 55 65 5 45 80 90 Højrerotation ved 70 med farveskift af 60 og 70: slut
AVL-træer contra Rød-Sort-træer Et rød-sort-træ er sædvanligvis mere effektivt: Indsættelse i et AVL-træ kræver (i værste tilfælde) 2 gennemløb af en vej (ned til et blad og tilbage til roden), mens indsættelse i et rød-sort-træ kan klares med 1 gennemløb. NB. I den rekursive implementation for et rød-sort-træ benyttes 2 gennemløb. Implementation af sletning i såvel AVL- som rød-sort-træer er vanskelig.