48 3. Dynamische Datenstrukturen 3.1 Listen (vgl. Informatik I) public class List<K extends Comparable<K>,D> implements MyCollection<K,D>{ protected static class ListElem<K,E> { ... s.u. ...} public class ListIterator implements Iterator<Pair<K,D>>{ ... s.u. ...} protected ListElem<K,D> head = null; public ListIterator iterator(){return new ListIterator();} public void insert(K key, D cont){ head = new ListElem<K,D>(kex, cont, head);} public D find(K key) throws Exception{ for(Pair<K,D> val: this) if (val.getFirst().equals(key)) return val.getSecond(); throw new Exception(key+" nicht gefunden");} public void delete(K key){ ... s.u. ...} } 49 Innere Klasse ListElem protected static class ListElem<K,E> { ListElem<K,E> succ; K key; E content; ListElem(K ky, E cont, ListElem<K,E> next){ succ = next; key = ky; content = cont;} } • Klasse Pair<K,T> siehe Webseite 50 Innere Klasse ListIterator public class ListIterator implements Iterator<Pair<K,D>>{ protected ListElem<K,D> current; public ListIterator(){current = head;} public boolean hasNext(){return (current != null);} public Pair<K,D> next(){ Pair<K,D> val = new Pair<K,D>(current.key, current.content); current = current.succ; return val;} public void remove(){ if (head.succ == current){head = current; return;} ListElem<K,D> elem = head; while (elem.succ.succ != current) elem = elem.succ; elem.succ = current;} } 51 Löschen public void delete(K key){ ListIterator iter = iterator(); Pair<K,D> value; while (iter.hasNext()){ value = iter.next(); if (value.getFirst().equals(key)) iter.remove();}} Aufwand der Listenoperationen: • für insert: O(1) (bei Vermeiden von Duplikaten: O(n)) • für find, delete: O(n) 52 3.2 Baumstrukturen 3.2.1 Binärer Suchbaum Idee: für Schlüssel k in Wurzel gilt: • k0 > k ∀k 0 im rechten Teilbaum • k0 ≤ k ∀k 0 im linken Teilbaum 43 24 16 78 39 42 53 Operationen im binären Suchbaum 43 43 24 24 78 42 78 insert(29,d) 16 42 78 delete(43) 16 39 24 39 29 16 42 39 29 • Operationen: Suchen, Einfügen, Löschen • Ziel: Aufwand (im Mittel) O(log n) bei n Schlüsseln • weitere Operationen: sortierte Ausgabe (eines Teils) der Schlüssel 54 Binärer Suchbaum in Java public class Tree<K extends Comparable<K>,D> implements MyCollection<K,D>{ protected Node<K,D> root = null; public void insert(K key, D data){ if (root == null) root = new Node<K,D>(key, data, null, null); else root.insertNode(key, data);} public D find(K key) throws Exception { if (root == null) throw new Exception("nicht gefunden"); else return root.findNode(key);} public void delete(K key) { if (root != null) root = root.deleteNode(key);} public LWR<K,D> iterator(){return new LWR<K,D>(root);} } 55 Klasse Node class Node<K extends Comparable<K>,D> { K key; D data; Node<K,D> left; Node<K,D> right; Node(K k, D d, Node<K,D> l, Node<K,D> r){ key=k; data=d; left=l; right=r;} public void insertNode(K k, D data){ ... s.u. ...} public D findNode(K k) throws Exception { ... s.u. ...} private Node<K,D> findMaxPred(){ ... s.u. ...} public Node<K,D> deleteNode(K k){ ... s.u. ...} } 56 Klasse Node: Suchen und Einfügen public void insertNode(K k, D data) { if (key.compareTo(k)<0) if (right == null) right = new Node<K,D>(k, data, null, null); else right.insertNode(k, data); else if (left == null) left = new Node<K,D>(k, data, null, null); else left.insertNode(k, data);} public D findNode(K k) throws Exception { if (key.compareTo(k)<0) if (right == null) throw new Exception(k+" nicht gefunden"); else return right.findNode(k); else if (k.compareTo(key)<0) if (left == null) throw new Exception(k+" nicht gefunden"); else return left.findNode(k); else return data;} 57 Klasse Node: Löschen private Node<K,D> findMaxPred() { if (right.right == null) return this; else return right.findMaxPred();} public Node<K,D> deleteNode(K k){ if (key.compareTo(k)<0){ if (right != null) right = right.deleteNode(k); return this;} else if (k.compareTo(key)<0) { if (left != null) left = left.deleteNode(k); return this;} if (left == null) return right; if (right == null) return left; if (left.right == null) {left.right = right; return left;} Node<K,D> maxPred = left.findMaxPred(); Node<K,D> max = maxPred.right; maxPred.right = max.left; max.left = left; max.right = right; return max;} 58 Iterator für Binärbaum import java.util.*; class LWR<K extends Comparable<K>,D> implements Iterator<Pair<K,D>>{ protected Stack<Node<K,D>> stack = new Stack<Node<K,D>>(); public LWR(Node<K,D> root){pushSpine(root);} private void pushSpine(Node<K,D> current){ while (current != null){ stack.push(current); current = current.left;}} public boolean hasNext(){return ! stack.empty();} public Pair<K,D> next(){ Node<K,D> current = stack.pop(); if (current.right != null) pushSpine(current.right); return new Pair<K,D>(current.key,current.data);} public void remove(){throw new UnsupportedOperationException();} } • analog: Durchlauf in Reihenfolge WLR (Präfix), LRW (Postfix), . . . 59 Aufwand im schlechtesten Fall • im schlechtesten Fall degeneriert der Baum zur Liste 1 2 ... n • daher: tfWind(n), tinsert (n), tdelete (n) ∈ O(n) W W 60 Aufwand im Mittel • o.B.d.A. Schlüssel 1, . . . , n • Annahme: alle Permutationen als Eingabefolge gleich wahrscheinlich • ignoriere Löschen; beachte: ? delete löscht vorzugsweise links ? also: Balance“ wird verschlechtert ” ? aber: faireres“ delete leicht möglich ” 61 Mittlere Pfadlänge wn bei Suche (i) • mittlere Pfadlänge wn , wenn i zuerst eingegeben: wn(i) 1 i−1 n−i = ·0+ · (wi−1 + 1) + · (wn−i + 1) n n n • für beliebiges zuerst eingegebenes Element: w0 =0 wn = = = = 1 n · n P 1 n · i=1 n P i=1 (i) für n ≥ 1 wn n−i n−1 ( i−1 · w + · w + i−1 n−i n n n ) n−1 n 1 n2 + n−1 n + n22 · n P i=1 n−1 P ((i − 1) · wi−1 + (n − i) · wn−i) i · wi i=1 62 zeige per Induktion: wn ≤ 4 · log2 n für n ≥ 1 √ n = 1: w1 = 0 = 4 · log2 1 1, . . . , n − 1 → n, n > 1 : wn = ≤ = ≤ n−1 n + n−1 n + n82 n−1 n n−1 n = n−1 n = n−1 n = n−1 n ≤ n−1 n + + 2 n2 n−1 P i=1 n−1 P i · wi i · log2 i i=1 bn/2c P 8 ( n2 i · log2 i + i=1 i · log2 i) i=bn/2c+1 8 n (log 2 2 2 n · bn/2c P · i=1 n bn 2 c(b 2 c+1) 2 + 8 n (log 2 2 2 n + 8 ((log2 n n2 − 8 b 2 c(b 2 c+1) 2 n2 − 8· n 4−1 n2 ·2 n n−1 P n i + log2 n · − 1) · + n−1 P i) i=bn/2c+1 log2 n · ( (n−1)n 2 n bn 2 c(b 2 c+1) 2 + log2 n · − n bn 2 c(b 2 c+1) )) 2 ( (n−1)n 2 − n bn 2 c(b 2 c+1) )) 2 + n82 log2 n · (n−1)n 2 2 ≤ 4 · log2 n + 4 · log2 n 2 63 Mittlerer Aufwand für das Suchen, Einfügen und Löschen • aus wn ≤ 4 · log2 n folgt: tfa ind(n) ≤ 4 · log2 n ∈ O(log n) • bei genauerer Rechnung: tfa ind(n) ≈ 1.386 · log2 n (vgl. Güting,Dieker, S. 137-141) • analog: tinsert (n) ∈ O(log n), tdelete (n) ∈ O(log n) A A 64 Variante: inhomogener Suchbaum • Daten nur in Blättern; innere Knoten enthalten nur Schlüssel • größenordnungsmäßiger Aufwand der Operationen unverändert • Löschen einfacher • innere Knoten benötigen weniger Speicher → interessant bei knappem Hauptspeicher (Blätter ggf. auf Sekundärspeicher) Beispiel: 42 27 (16, "Bob") (72, "Jim") (42, "Sam") 65 3.2.2 Balancierte Suchbäume • Ziel: auch im schlechtesten Fall logarithmischer Aufwand für Suchen, Einfügen, Löschen • Idee: Baum bei Einfügen und Löschen geeignet ausbalancieren“ ” • Ansätze ? höhenbalancierte Bäume (z. B. AVL) ? gewichtsbalancierte Bäume ? B-Baum(-Variante) 66 3.2.2.1 AVL-Bäume • nach Adelson-Velskii, Landis (1962) • höhenbalancierter, binärer Suchbaum • Idee: für jeden Knoten unterscheiden sich die Höhen der Teilbäume um ≤ 1 67 Berechnung der maximalen Höhe • konstruiere zu vorgegebener Höhe einen AVL-Baum mit minimaler Knotenanzahl Menge Fh der Fibonacci-Bäume der Höhe h: • ({v}, ∅) ∈ F0 • ({v1, v2}, {(v1, v2)}) ∈ F1 • T1 = (V1, E1) ∈ Fh−1, T2 = (V2, E2) ∈ Fh−2 ⇒ ({v} ∪ V1 ∪ V2, E1 ∪ E2 ∪ {(v, v1), (v, v2)}) ∈ Fh falls h ≥ 2, v1 Wurzel von T1, v2 Wurzel von T2 • keine anderen Bäume sind Fibonacci-Bäume 68 Fibonacci-Bäume Minimale Knotenanzahl eines AVL-Baums #Knoten k(h) von t ∈ Fh: k(0) = 1 k(1) = 2 k(h) = k(h − 1) + k(h − 2) + 1 für h ≥ 2 • k(h) ist die minimale Knotenanzahl eines AVL-Baums der Höhe h 69 Vergleich: Fibonacci-Zahlen und k(h) f ib(0) = 0 f ib(1) = 1 f ib(h) = f ib(h − 1) + f ib(h − 2) für h ≥ 2 h fib(h) k(h) 0 0 1 1 1 2 2 1 4 n ≥ k(h) = f ib(h + 3) − 1 > √ ⇒ 5 · (n + 2) > Φh+3 √ ⇒ logΦ( 5(n + 2)) − 3 > h 3 2 7 h+3 Φ√ 5 4 3 12 −2 5 5 20 6 8 33 7 13 54 8 21 88 9 34 143 10 55 232 (mit Φ = ⇒ h ∈ O(log n) genauer: h ≤ 1.44 · log2 n + 1 damit: tfWind(n) ∈ O(log n), da Suchen wie im binären Suchbaum √ 1+ 5 2 , s. Knuth) 70 Einfügen in AVL-Baum • beim Einfügen eines Knotens kann die Balance eines Knotens unzulässig werden • Balance durch eine lokale Reorganisation reparabel • (bis auf Symmetrie) zwei unterschiedliche Situationen y x h(T1)+2 −2 x −1 T3 y h(T2)+3 oder h(T3)+3 1 T4 z −1 oder 1 T1 T2 T3 0 h(T1)+1 T1 T2 x −2 y fertig! Rechtsrotation(y) T2 T1 0 z Doppelrotation links−rechts(x) = Linksrotation(y) ° Rechtsrotation(x) 0 y −1 oder 0 T1 T2 T3 x T3 0 oder 1 T4 h(T2)+2 oder h(T3)+2 71 Beispiel: AVL-Baum-Operationen 42 42 insert(28) 27 16 58 29 35 62 29 58 27 37 35 16 28 37 29 28 insert(4) 27 16 4 delete(29) 42 28 62 35 16 58 37 4 62 42 27 35 58 37 62 72 AVL-Baum in Java public class AVL<K extends Comparable<K>,D> implements MyCollection<K,D>{ class Node{ // member class K key; D content; Node left; Node right; int balance; // -1, 0 oder 1 Node(K k, D c, Node l, Node r, int b){ key=k; content=c; left=l; right=r; balance=b;} außerdem: insertNode, deleteNode, rotateLeft, rotateRight, findMax, rebalanceLeft usw. s. Webseite } // Ende von class Node protected Node root = null; private boolean flag = false; public public public public } // Hilfsvariable für Einfügen u. Löschen D find(K key) throws Exception{... analog zu Tree ...} void insert(K key, D data){... s. Webseite ...} void delete(K key){... s. Webseite ...} AVLLWR<K,D> iterator(){return new AVLLWR<K,D>(root);} 73 Aufwand von Einfügen und Löschen • wie gezeigt, ist die Höhe eines AVL-Baums logarithmisch • Beobachtung: durch Reorganisation sinkt die Höhe an der Rotationsstelle um 1 • ⇒ beim Einfügen ist höchstens eine lokale Reorganisation mit konstantem Aufwand nötig ⇒ tinsert (n) ∈ O(log n) W • beim Löschen ist erforderlich: ? höchstens ein Aufruf von findMax (mit Aufwand O(log n)) ? an jedem (von O(log n)) besuchten Knoten höchstens eine Reorganisation mit konstantem Aufwand: ⇒ tdelete (n) ∈ O(log n) W 74 3.2.2.2 B-Bäume • von Bayer, McCreight 1970 • 2m + 1-ärer Suchbaum • besonders wichtig für Sekundärspeicher (Knoten = ˆ Seite) • Varianten für Hauptspeicher: 2-3-4-Bäume, 2-3-Bäume, Rot-Schwarz-Bäume 75 B-Baum der Ordnung m (m ∈ IN+) • jeder Knoten enthält ≤ 2m Schlüssel • jeder Knoten außer der Wurzel enthält ≥ m Schlüssel • jeder innere Knoten mit l Schlüsseln hat l + 1 Nachfolger • alle Blätter haben die gleiche Höhe Beispiel: 30 10 20 24 27 32 34 50 40 45 70 63 66 80 90 76 B-Baum in Java public class Btree<K extends Comparable<K>,D> implements MyCollection<K,D{ protected Bnode<K,D> root = null; protected int ord; public Btree(int n){ord = n;} public D find(K k) throws Exception { if (root == null) throw new Exception(k+" nicht gefunden"); return root.searchNode(k);} public void insert(K k, D c){... s.u....} public void delete(K k) {... s.u....} public BBLWR<K,D> iterator(){return new BBLWR<K,D>(root);} } 77 Klasse Bnode class Bnode<K extends Comparable<K>, D> { int count = 0; int ord; Entry<K,D> entry[]; public Bnode(int n){ord = n; entry = new Entry[2*ord+2];} D searchNode(K k) throws Exception {... s.u....} Entry<K,D> insertNode(K k, D c) {... s.u....} boolean deleteNode(K k){... s.u....} private Entry<K,D> findMin(){ if (entry[0].next != null) return entry[0].next.findMin(); else return entry[1];} private boolean underflow(int i) {... s. Webseite ...} // ggf. Ausgleich bzw. Verschmelzen mit linkem Nachbarn } 78 Klasse Entry class Entry<K extends Comparable<K>, D> { K key; D content; Bnode<K,D> next; Entry(K k, D d, Bnode<K,D> n){key=k; content=d; next=n;} } 79 B-Baum: Suchen • suche die Position von k im betrachteten Knoten count=3 0 • wenn k gefunden: fertig entry für Überlauf frei 1 2 3 30 50 70 4 5 • sonst suche im Kindknoten “gemäß der Position” von k weiter D searchNode(K k) throws Exception { int i=1; // alternativ: binaere Suche while (i<=count && entry[i].key.compareTo(k)<0) i++; if (i<=count && entry[i].key.equals(k)) return entry[i].content; if (entry[--i] != null) { if (entry[i].next == null) throw new Exception(k+" nicht gefunden");} else throw new Exception(k+" nicht gefunden"); return entry[i].next.searchNode(k);} 80 Einfügen in B-Bäumen • Blatt ansteuern wie bei Suche und dort einfügen • überlaufende Knoten teilen • Trenneintrag“ in Vorgängerknoten einfügen → ggfs. neuer Überlauf ” • bei Überlauf der Wurzel: neue Wurzel über Teile der alten setzen 81 Beispiel: Einfügen in B-Bäumen 30 10 20 24 27 32 34 50 40 70 45 63 66 80 90 insert(95) insert(42) 30 10 20 24 27 32 40 34 50 42 70 45 63 66 80 90 95 insert(22) 40 22 10 20 30 24 27 50 32 34 42 45 63 70 66 80 90 95 82 Einfügen in B-Baum: Klasse Btree public void insert(K k, D c) { if (root == null) root = new Bnode<K,D>(ord); Entry<K,D> newEntry = root.insertNode(k, c); if (newEntry != null) { Bnode<K,D> oldroot = root; root = new Bnode<K,D>(ord); root.count = 1; root.entry[0] = new Entry<K,D>(null, null, oldroot); root.entry[1] = newEntry;}} 83 Einfügen in B-Baum: Klasse Bnode Entry<K,D> insertNode(K k, D c){ int i = 1; Entry<K,D> newEntry; while (i <= count && entry[i].key.compareTo(k)<0) i++; if (entry[--i] == null) entry[i] = new Entry<K,D>(null, null, null); if (entry[i].next != null) newEntry =entry[i].next.insertNode(k, c); else newEntry = new Entry<K,D>(k, c, null); if (newEntry //newEntry for (int j entry[j] entry[i+1] != null) { an Position i+1 einfügen = ++count; j>i+1; j--) = entry[j-1]; = newEntry; if (count > 2*ord){ // Knoten teilen Bnode<K,D> brother = new Bnode<K,D>(ord); int l = 0; for (int j = count/2+1; j <= count; j++) brother.entry[l++] = entry[j]; count /= 2; brother.count = count; return new Entry<K,D>(entry[count+1].key, entry[count+1].content,brother); else return null;} else return null; } 84 Löschen im B-Bäumen • Blatteintrag direkt löschbar • gelöschten Eintrag in anderem Knoten durch nächstgrößeren Eintrag ersetzen • bei Unterlauf: benachbarte Knoten verschmelzen oder Einträge von Nachbarn herüberholen • Verschmelzen kann Unterlauf des Vorgängers auslösen • bei Unterlauf der Wurzel wird sie gelöscht 85 Ausgleich 60 (m=3) − 1 ... ... 20 30 40 45 50 55 2 3 4 5 6 7 − 75 8 9 80 10 50 − 1 20 30 40 45 − 55 60 75 2 3 4 5 6 7 8 9 80 10 86 Verschmelzen 50 (m = 2) 90 - 20 30 - 1 2 3 4 60 6 5 90 - 20 30 50 60 1 2 3 4 5 6 87 Beispiel: Löschen im B-Bäumen 40 22 10 20 30 24 50 27 32 34 42 45 63 70 66 80 90 95 delete (66) 40 22 10 20 30 24 50 27 32 34 42 45 63 80 70 90 95 delete (45) 22 10 20 24 27 30 40 32 34 80 42 50 63 70 90 95 delete (40) 22 10 20 24 27 30 42 32 34 80 50 63 70 90 95 88 Löschen in B-Baum: Klasse Btree public void delete(K k){ if (root != null){ root.deleteNode(k); if (root.count == 0) { if (root.entry[0].next != null && root.entry[0].next.count > 0) root = root.entry[0].next; else if (root.entry[1].next != null && root.entry[1].next.count >0) root = root.entry[1].next; else root = null;}}} 89 Löschen in B-Baum: Klasse Bnode boolean deleteNode(K k){ boolean flag; // underflow? int i = 1; while (i<=count && entry[i].key.compareTo(k)<0) i++; if (i<=count && entry[i].key.equals(k)){ if (entry[i].next == null){ //lösche in Blatt for (int j = i; j<count; j++) entry[j] = entry[j+1]; count--; return (count<ord);} else { //lösche in innerem Knoten Entry<K,D> min = entry[i].next.findMin(); entry[i].key = min.key; entry[i].content = min.content; flag = entry[i].next.deleteNode(min.key);} else if (entry[--i].next == null) return false; else flag = entry[i].next.deleteNode(k); if (flag) return underflow(i); // underflow s. Webseite else return false;} } 90 Aufwand • für # Knoten v gilt: v ≤ dn/me + 1 sowie v ≥ 2 ∗ (m + 1)h−1 (1) (2) daher: h ≤ 1 + logm+1 v2 ∈ O(log n) f ür festes m > 0 ≤ 1 + logm+1 dn/me+1 2 • z. B. bei n = 2 ∗ 106 und m = 100: h ≤ 3 • da Aufwand für Suchen, Einfügen und Löschen proportional zur Höhe: tsearch (n), tinsert (n), tdelete (n) ∈ O(log n) W W W • da jeder Knoten (außer Wurzel) mindestens m von 2m möglichen Einträgen (wichtig bei Sekundärspeicher!) enthält: Speicherplatzausnutzung α ≥ 50% • durch leichte Modifikation von insert und delete: α ≥ 66% 91 3.2.3 Optimale Suchbäume • manchmal sind die Suchwahrscheinlichkeiten der Schlüssel bekannt • in der Regel ist bei solchen Anwendungen die Menge S = {s1, . . . , sn} der gespeicherten Schlüssel fest (weder Einfügen noch Löschen) (o.B.d.A. si < sj falls i < j, S ⊆ IN; s0 := −∞, sn+1 := ∞) • Beispiel: Schlüsselworttabelle eines Compilers • Idee: baue Suchbaum so, dass Gesamtsuchzeit minimal pi : Wahrscheinlichkeit, dass si gesucht (i = 1, . . . , n) qi : Wahrscheinlichkeit, dass Schlüssel s mit si < s < si+1 gesucht (i = 0, . . . , n) n X i=1 pi + n X j=0 qj = 1 92 Zugriffswahrscheinlichkeiten und Weglänge • Wahrscheinlichkeiten oft experimentell bestimmt • wenn bei m Versuchen ? ai-Mal si gesucht ? bj -Mal s ∈ (sj , sj+1) gesucht ai ? dann Annahme: pi = m , qj = (i = 1, . . . , n) (j = 0, . . . , n), bj m • betrachte Baum T : ? sei hi: # Knoten, auf Weg von Wurzel zu si ? h0i: 1+ Anzahl besuchter Knoten bei Suche nach s ∈ (si, si+1) ? dann: kummulierte gewichtete Weglänge w von T w= n X i=1 • Ziel: minimiere w ai ∗ hi + n X j=0 bj ∗ h0j (i = 1, . . . , n) (i = 0, . . . , n) 93 Bestimmung des optimalen Suchbaums • Ansatz (dynamische Programmierung): ausgehend von optimalen Bäumen für Teile M1, M2 des Wertebereichs IN bestimme optimalen Baum für M1 ∪ M2 • Notation: Ti,j : ci,j : wi,j : ci,j = optimaler Suchbaum für Wertebereich (si, sj+1) und gespeicherte Schlüssel si+1, . . . , sj # Besuche von Ti,j kumulierte gewichtete Weglänge von Ti,j j P k=i+1 ak + j P bk (0 ≤ i ≤ j ≤ n) k=i wi,i = bi (i = 0, . . . , n) wi,j = ci,j + minjk=i+1(wi,k−1 + wk,j ) (0 ≤ i < j ≤ n) • der optimale Suchbaum T0,n besteht aus den Teilbäumen Ti,j , deren wi,j “zu w0,n beitragen” 94 Aufwand • es gibt ≤ n2 Werte wi,j • jeder in j − i ≤ n Schritten bestimmbar (min!) ⇒ O(n3) Schritte • verbesserbar zu O(n2) (→ Knuth) durch verkleinerten Suchbereich bei min-Bildung Bemerkung: • wenn Einfügen, Löschen und sich ändernde Zugriffswahrscheinlichkeiten erlaubt: → selbstorganisierende Bäume (→ Mehlhorn, Bd. III, Kap. 6) d.h. gewichtsbalancierte Bäume, bei denen Gewicht abh. von Zugriffshäufigkeit 95 Beispiel: Optimaler Suchbaum (m = 100) i 1 2 3 si 27 42 68 ai 10 20 30 j 0 1 2 3 (sj , sj+1) (−∞, 27) (27, 42) (42, 68) (68, ∞) bj 5 5 10 20 Berechnung von ci,j und wi,j : ci,j : 1 2 3 j\i 0 0 5 − − − 1 20 5 − − wi,j : 2 50 35 10 − 3 100 85 60 20 1 2 3 j\i 0 0 5 − − − 1 30 5 − − 2 90 50 10 − 3 210 155 90 20 96 Optimaler Baum im Beispiel T 68 0,3 30 42 − 40 40 27 − 30 30 − − 20 20 insgesamt: w 0,3 zum Vergleich: = 210 42 20 27 68 20 60 − − − − 15 15 30 insgesamt: w 0,3 60 = 220 97 3.2.4 Alfabetische Bäume (Tries) bei bisherigen Suchbäumen: • Knoten enthalten vollständige Schlüssel • bei langen Schlüsseln großer Platzverbrauch Alfabetische Bäume: • jede Kante mit einem Zeichen des Schlüssels beschriftet • Suche, Einfügen und Löschen folgen Kanten mit gewünschter Beschriftung • gut, falls viele Schlüssel gleiche Präfixe haben Beispiel: a f affe x axt h k a a s u hase haus kahn o korn u kuh 98 3.2.5 Mehrdimensionale Bäume • wichtig in : (relationalen) Datenbanken, Data Warehouses, Computergrafik, Geo-Informationssytemen, OLAP • Wertebereich D := D1 × . . . × Dk • Schlüsselmenge S = {s1, . . . , sn} ⊂ D 99 Anfragen • exakte Suche (exact match query) vi ∈ Di search(v1, . . . , vk ) Beispiel: search(137, ”Koetter”) • partielle Übereinstimmungssuche (partial match query) vi ∈ Di ∪ {∗} search(v1, . . . , vk ) Beispiel: search(∗, ”Koetter”) • Bereichssuche (range query) search(r1, . . . , rk ) wobei Intervall ri = [vi, vi0 ] ⊂ Di mit vi, vi0 ∈ Di Beispiel: search([0, 9999], [”Kaemper”, ”Koetter”]) weitere Operationen: • insert(v1, . . . , vk ) • delete(. . .) vi ∈ Di Parameter wie bei Anfragen 100 kd-Baum • binärer Suchbaum • meist inhomogene Variante (Daten in Blättern, in inneren Knoten nur Schlüssel), da Löschen leichter • Knoten v auf Stufe iv (mit iv ≥ 0) teilt Suchraum gemäß dv ∈ D(iv mod k)+1: ? für jeden Schlüssel s = (x1, . . . , xk ) im Teilbaum Lv links von v ist x(iv mod k)+1 ≤ dv ? analog im Teilbaum Rv rechts von v: x(iv mod k)+1 > dv 101 Beispiel: kd-Baum 42 (k = 2) m 27 (27,a) g 32 (35,a) (9,x) (50,g) (50,x) p (35,n) (33,x) induzierte Aufteilung des Suchraums: 60 50 40 30 20 10 a g mn p x 102 Exakte Suche nach (x1, . . . , xk ) • an innerem Knoten v suche in Lv weiter, wenn xiv mod k+1 ≤ dv sonst suche in Rv weiter • an Blatt vergleiche (x1, . . . , xk ) mit Eintrag → Aufwand wie beim eindimensionalen Suchbaum temq A (n) ∈ O(log n) falls h ∈ O(log n) 103 Partielle Übereinstimmungs-Suche nach (x1, . . . , xk ) • Annahme: t < k Komponenten 6= ∗ • an innerem Knoten v: ? suche in Lv weiter, wenn ∗ = 6 xiv mod k+1 ≤ dv ? suche in Rv weiter, wenn ∗ = 6 xiv mod k+1 > dv ? suche in Lv und Rv , wenn xiv mod k+1 = ∗ • an Blatt: vergleiche Eintrag mit Suchmuster Aufwand: h h k−t k ) = O(t · (2 ) tpmq (n) ∈ O(t · (2 ) A k−t k t ) = O(t · n1− k ) falls h = log2 n und 0 < t < k 104 Bereichsanfrage nach ([u1, o1], . . . , [uk , ok ]) • an innerem Knoten v: ? suche in Lv weiter, wenn oiv mod k+1 ≤ dv ? suche in Rv weiter, wenn uiv mod k+1 > dv ? sonst suche in Lv und Rv weiter (dann: uiv mod k+1 ≤ dv < oiv mod k+1) • an Blatt: vergleiche Eintrag mit Suchmuster • Aufwand abhängig von Bereichsgröße Einfügen: Löschen: (zwischen O(log n) und O(n)) tinsert (n) ∈ O(log n) A tdelete (n) ∈ O(log n) A falls h = log2 n falls h = log2 n, Bereich klein Variante: Quad-Tree • jeder innere Knoten teilt Suchraum in 4 Teile (Quadranten)