Dynamische Datenstrukturen

Werbung
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)
Herunterladen