Satz

Werbung
Algorithmen und Datenstrukturen
Werner Struckmann
Wintersemester 2005/06
6. Bäume
6.1 Bäume
6.2 Binäre Suchbäume
6.3 Ausgeglichene Bäume
6.4 Heapsort
Listen und Bäume
Listen und Bäume:
◮
Listen: Jedes Listenelement besitzt genau einen Vorgänger
und einen Nachfolger. Ausnahmen: Das erste Element besitzt
keinen Vorgänger und das letzte keinen Nachfolger.
◮
Bäume (engl.: trees): Jedes Element besitzt genau einen
Vorgänger und mehrere Nachfolger. Ausnahmen: Das erste
Element besitzt keinen Vorgänger und die letzten Elemente
keine Nachfolger.
Anwendungen von Bäumen:
6.1 Bäume
◮
Ausdrücke,
◮
Speicherung von Mengen,
◮
hierarchisch strukturierte Daten, z. B. Dateibäume,
◮
Sortieralgorithmen.
6-1
Grundbegriffe
◮
Die Elemente eines Baums
heißen Knoten.
◮
Das erste Element eines Baums
ist die Wurzel.
◮
Die letzten Elemente werden
Blätter genannt.
◮
Innere Knoten sind Elemente,
die keine Blätter sind.
11
5
17
2
7
6
◮
6.1 Bäume
13
22
16
Jeder Knoten kann einen
Schlüssel und weitere
Informationen speichern.
6-2
Grundbegriffe
◮
◮
◮
6.1 Bäume
Nachfolgerknoten werden auch
Kindknoten und
Vorgängerknoten Vaterknoten
genannt.
Bäume sind wie Listen
dynamische Datenstrukturen.
Knoten können eingefügt und
gelöscht werden.
Bäume sind wie Listen rekursive
Datenstrukturen. Jeder Knoten
kann als die Wurzel eines
(Teil-)Baums angesehen
werden.
11
5
17
2
7
6
13
22
16
6-3
Pfade
11
◮
◮
In jedem Baum gibt es von der
Wurzel zu einem beliebigen
Knoten genau einen Pfad.
◮
Jeder Baum ist
zusammenhängend.
◮
6.1 Bäume
Ein Pfad in einem Baum ist eine
Folge aufeinanderfolgender
Knoten. Die Anzahl der Knoten
eines Pfades minus 1 heißt
dessen Länge.
Jeder Baum ist zyklenlos.
5
17
2
7
6
13
22
16
[17, 13, 16] ist der Pfad
vom Knoten mit
Schlüssel 17 zum
Knoten mit Schlüssel
16. Die Länge dieses
Pfades beträgt 2.
6-4
Niveau und Höhe
◮
◮
◮
◮
6.1 Bäume
Die Länge des Pfades von
der Wurzel zu einem Knoten
heißt Niveau des Knotens.
Knoten, die auf dem gleichen
Niveau liegen, heißen
Nachbarknoten.
Die Höhe eines Baums ist
das Maximum der Längen
aller Pfade im Baum.
Die Höhe eines Knotens ist
definiert als das Maximum
der Längen aller Pfade von
diesem Knoten zu einem
Blatt.
11
Niveau 0
5
17
2
7
6
13
Niveau 1
22
16
Niveau 2
Niveau 3
[11, 17, 13, 16] und
[11, 5, 7, 6] sind die
längsten Pfade. Da ihre
Länge 3 beträgt, ist die
Höhe des Baumes 3. Die
Höhe von Knoten „5“ ist 2.
6-5
Spezielle Bäume
Ist k ∈ N die Maximalzahl der Kinder eines Knotens, so spricht
man von einem k -nären Baum.
Beispiele:
◮
Binärbäume (binäre Bäume) sind 2-näre Bäume.
Ternärbäume (ternäre Bäume) sind 3-näre Bäume.
◮
B-Bäume werden später behandelt.
Sind die Kinder eines Knotens in einer definierten Weise geordnet,
so spricht man von einem geordneten Baum.
Beispiele:
6.1 Bäume
◮
Binäre Suchbäume sind geordnete Binärbäume.
◮
Heaps werden später behandelt.
6-6
Binärbäume
Binärbäume sind 2-näre Bäume. Die
maximale Anzahl von Kindern eines
Knotens ist 2. Die Kinder eines
Knotens bilden die Wurzeln des
linken und des rechten Teilbaums
des Knotens.
11
5
17
2
7
6
13
22
16
Höhe des Baumes: 3
Max. Anzahl Knoten: 15
Max. Anzahl Blätter: 8
Max. Anzahl innerer
Knoten: 7
6.1 Bäume
6-7
Binärbäume als abstrakter Datentyp
type BinTree (T )
import Bool
operators
empty :→ BinTree
bin : BinTree × T × BinTree → BinTree
left : BinTree → BinTree
right : BinTree → BinTree
value : BinTree → T
empty ? : BinTree → Bool
axioms ∀t ∈ T , ∀x , y ∈ BinTree
left (bin(x , t , y )) = x
right (bin(x , t , y )) = y
value (bin(x , t , y )) = t
empty ?(empty ) = true
empty ?(bin(x , t , y )) = false
6.1 Bäume
6-8
Eigenschaften binärer Bäume
Bezeichnungen:
◮
Höhe des Baums: h
◮
Anzahl der Knoten: n
In einem nichtleeren binären Baum gilt:
◮
Maximalzahl der inneren Knoten: 2h − 1
◮
Maximalzahl der Blätter: 2h
◮
h + 1 ≤ n ≤ 2h +1 − 1
◮
Folgerung:
log2 (n + 1) − 1 ≤ h ≤ n − 1
Das heißt: h liegt zwischen Θ(log(n)) und Θ(n).
6.1 Bäume
6-9
Traversierung eines Binärbaums
Unter der Traversierung eines Binärbaums versteht man ein
Verfahren, bei dem jeder Knoten eines Baums genau einmal
besucht wird.
◮
Beim Tiefendurchlauf (engl.: depth-first-search, DFS) wird
zuerst in die „Tiefe“ und erst dann in die „Breite“ gegangen.
Man besucht von einem Knoten erst die Kindknoten und setzt
dort das Verfahren rekursiv fort. Die Verfahren unterscheiden
sich darin, wann der Knoten selbst bearbeitet wird.
◮
◮
◮
◮
6.1 Bäume
Inorder-Durchlauf
Preorder-Durchlauf
Postorder-Durchlauf
Bei der Breitensuche (engl.: breadth-first-search, BFS) geht
man von einem Knoten zuerst zu allen Nachbarknoten, bevor
die Kindknoten besucht werden.
6-10
Tiefendurchlauf
◮
◮
◮
6.1 Bäume
Inorder-Durchlauf:
linker Teilbaum, Knoten, rechter
Teilbaum
2, 5, 6, 7, 11, 13, 16, 17, 22
Preorder-Durchlauf:
Knoten, linker Teilbaum, rechter
Teilbaum
11, 5, 2, 7, 6, 17, 13, 16, 22
Postorder-Durchlauf:
linker Teilbaum, rechter
Teilbaum, Knoten
2, 6, 7, 5, 16, 13, 22, 17, 11
11
1.
2.
5
17
2
7
6
13
22
16
6-11
Inorder-Durchlauf
proc inorder(x) begin
if x , nil then
inorder(left(x));
bearbeite(x);
inorder(right(x));
fi;
end
6.1 Bäume
6-12
Preorder- und Postorder-Durchlauf
proc preorder(x) begin
if x , nil then
bearbeite(x);
preorder(left(x));
preorder(right(x));
fi;
end
proc postorder(x) begin
if x , nil then
postorder(left(x));
postorder(right(x));
bearbeite(x);
fi;
end
6.1 Bäume
6-13
Breitendurchlauf
11
1.
◮
Levelorder-Durchlauf:
11, 5, 17, 2, 7, 13, 22, 6, 16
2.
3.
6.1 Bäume
5
17
2
7
6
13
22
16
6-14
Levelorder-Durchlauf
proc levelorder(x) begin
var q: queue;
enter(q,x);
while not isEmpty(q) do
y ← front(q);
leave(q);
bearbeite(y);
if left(y) , nil then enter(q,left(y));
if right(y) , nil then enter(q,right(y));
od;
end
6.1 Bäume
6-15
Binäre Suchbäume
◮
Der Wert eines Knotens ist ein
eindeutiger Schlüssel aus einer
Grundmenge.
◮
Die Grundmenge ist durch ≤
bzw. < total geordnet.
◮
Die Knoten enthalten
zusätzliche Nutzdaten.
◮
Die Anordnung der Knoten im
Baum basiert auf der
Ordnungsrelation.
6.2 Binäre Suchbäume
Person 5
root
11
5
Person 11
2
7
17
13
22
Person 2
Person 6
Person 17
Person 22
6
Person 7
16
Person 16
Person 13
6-16
Binäre Suchbäume
Ein binärer Suchbaum ist ein Binärbaum, bei dem für jeden Knoten
v , seinen linken Teilbaum b1 und seinen rechten Teilbaum b2 gilt:
Für jeden Knoten v1 aus b1 ist v1 < v und für jeden Knoten v2 aus
b2 ist v2 > v .
11
< 11
> 11
5
17
2
7
6
13
22
16
Die Nutzdaten werden im Folgenden nicht betrachtet.
6.2 Binäre Suchbäume
6-17
Basisalgorithmen für binäre Suchbäume
◮
Suchen eines Knotens,
◮
Bestimmen des Minimums oder Maximums,
◮
Bestimmen des Nachfolgers oder Vorgängers eines Knotens,
◮
Einfügen eines Knotens,
◮
Löschen eines Knotens.
Die Operationen müssen die Suchbaumeigenschaft
aufrechterhalten.
6.2 Binäre Suchbäume
6-18
Suchen eines Knotens (rekursiv)
11
7 < 11
5
17
7>5
2
7
6
13
22
16
func search(x,k) begin
if x = nil oder k = schlüssel(x)
then return x; fi;
if k < schlüssel(x)
then return search(links(x),k);
else return search(rechts(x),k); fi;
end
6.2 Binäre Suchbäume
6-19
Suchen eines Knotens (iterativ)
11
7 < 11
5
17
7>5
2
7
6
13
22
16
func search(x,k) begin
while x , nil und k , schlüssel(x)
do if k < schlüssel(x)
then x ← links(x);
else x ← rechts(x); fi;
od;
return x;
end
6.2 Binäre Suchbäume
6-20
Minimum und Maximum
11
5
17
2
7
6
13
22
16
func minimum(x) begin
while links(x) , nil do x ← links(x); od;
return x;
end
func maximum(x) begin
while rechts(x) , nil do x ← rechts(x); od;
return x;
end
6.2 Binäre Suchbäume
6-21
Nachfolger und Vorgänger
Bestimmung des Nachfolgers
(Knoten mit dem nächsthöheren
Schlüssel) eines Knotens k :
◮
◮
Falls k kein rechtes Kind hat, ist
der Nachfolger der nächste
Vorgänger von k , dessen linkes
Kind k oder ein Vorgänger von
k ist. Falls k das Maximum im
Baum ist, existiert kein
derartiger Vorgänger.
Falls k ein rechtes Kind hat, ist
der Nachfolger das Minimum im
vom rechten Kind
aufgespannten Teilbaum.
6.2 Binäre Suchbäume
Nachfolger von 7:
11
5
17
2
7
6
13
22
16
6-22
Nachfolger und Vorgänger
func successor(x) begin
if rechts(x) , nil
then return minimum(rechts(x));
fi;
y ← vater(x);
while y , nil und x = rechts(y)
do x ← y;
y ← vater(y);
od;
return y;
end
Die Bestimmung des Vorgängers erfolgt analog.
6.2 Binäre Suchbäume
6-23
Einfügen eines Knotens
Einfügen des Schlüssels 12:
11
12 > 11
5
17
12 < 17
2
7
13
22
12 < 13
6
6.2 Binäre Suchbäume
12
16
6-24
Einfügen eines Knotens
proc insert(T, z): begin
y ← nil;
x ← wurzel(T);
while x , nil
do y ← x;
if schlüssel(z) < schlüssel(x)
then x ← links(x);
else x ← rechts(x); fi; od;
vater(z) ← y;
if y = nil then wurzel(T) ← z;
else if schlüssel(z) < schlüssel(y)
then links(y) ← z;
else if schlüssel(z) > schlüssel(y)
then rechts(y) ← z;
else error(”Doppelter Schlüssel”);
fi; fi; fi;
end
6.2 Binäre Suchbäume
6-25
Löschen eines Knotens
Beim Löschen eines Knotens können 3 Fälle auftreten.
Der Knoten hat keine Kinder:
Er wird einfach gelöscht.
Der Knoten hat ein Kind:
Er wird ausgeschnitten.
11
11
5
17
2
7
6
6.2 Binäre Suchbäume
13
5
22
16
17
2
7
6
13
22
16
6-26
Löschen eines Knotens
Der Knoten hat zwei Kinder:
Aus dem rechten Teilbaum wird der Nachfolger bestimmt und
dieser dort gelöscht. Dieser Nachfolger hat höchstens ein Kind.
Der Nachfolger wird anstelle des zu löschenden Knotens
verwendet. Alternativ kann der Vorgänger im linken Teilbaum
genommen werden.
11
5
17
2
7
6
6.2 Binäre Suchbäume
13
22
16
6-27
Löschen eines Knotens
func delete(T, z): Knoten begin
if links(z) = nil oder rechts(z) = nil
then y ← z; else y ← successor(z); fi;
if links(y) , nil
then x ← links(y); else x ← rechts(y); fi;
if x , nil
then vater(x) ← vater(y); fi;
if vater(y) = nil
then wurzel(T) ← x;
else if y = links(vater(y))
then links(vater(y)) ← x;
else rechts(vater(y)) ← x; fi; fi;
if y , z
then schlüssel(z) ← schlüssel(y);
kopiere Nutzdaten; fi;
return y;
end
6.2 Binäre Suchbäume
6-28
Laufzeiten der Basisalgorithmen
Die Analyse der Algorithmen liefert den
Satz: Die Laufzeiten der Algorithmen
◮
Suchen eines Knotens,
◮
Minimum, Maximum,
◮
Nachfolger, Vorgänger,
◮
Einfügen eines Knotens und
◮
Löschen eines Knotens
liegen bei geeigneter Implementierung in der Zeit O (h ), wobei h
die Höhe des binären Suchbaums ist.
6.2 Binäre Suchbäume
6-29
Random-Tree-Analyse
Annahmen:
1. Die Schlüssel sind paarweise verschieden.
2. Die Bäume entstehen durch Einfüge-, aber nicht durch
Löschoperationen.
3. Jede der n! Permutationen der Eingabe ist
gleichwahrscheinlich.
Es gilt der
Satz: Für die mittlere Knotentiefe P (n) in einem zufällig erzeugten
binären Suchbaum gilt P (n) = O (log(n)).
Es gilt sogar schärfer der
Satz: Die erwartete Höhe eines zufällig erzeugten binären
Suchbaums mit n Schlüsseln ist O (log(n)).
Beweis: s. Cormen et al., S. 266–269.
6.2 Binäre Suchbäume
6-30
Gestaltsanalyse
Satz: Für die Anzahl bn der strukturell verschiedenen Binärbäume
gilt:
! 


falls n = 0,
1 2n
1,
=
bn =
Pn−1


n+1 n
falls n > 0.
k =0 bk bn−1−k ,
Annahmen:
1. Die Schlüssel sind paarweise verschieden.
2. Jeder der bn Binärbäume ist gleichwahrscheinlich.
Satz: Der mittlere Abstand eines
von der Wurzel eines
√Knotens
Binärbaums mit n Knoten ist O
n .
Beweis: s. Ottmann/Widmayer, S. 271–275.
6.2 Binäre Suchbäume
6-31
Ausgeglichene Bäume
◮
Höhe des Binärbaums: h
◮
Anzahl der Knoten: n
◮
Es gilt: log2 (n + 1) − 1 ≤ h ≤ n − 1.
1
2
Binäre Suchbäume können zu
Listen „entarten“:
3
5
8
13
21
6.3 Ausgeglichene Bäume
6-32
Ausgeglichene Bäume
Definition: M sei eine Klasse von Bäumen. Für T ∈ M sei n(T ) die
Knotenzahl und h (T ) die Höhe von T . M heißt ausgeglichen
(ausgewogen), wenn die beiden folgenden Bedingungen erfüllt
sind:
1. Ausgeglichenheitsbedingung:
∃c > 0 ∀T ∈ M : h (T ) ≤ c · log n(T ).
2. Rebalancierungsbedingung:
Falls eine Einfüge- oder Löschoperation, ausgeführt in einem
Baum T ∈ M , einen unausgeglichenen Baum T ′ < M erzeugt,
dann soll es möglich sein, T ′ mit Zeitaufwand O (log n) zu
einem Baum T ′′ ∈ M zu rebalancieren.
6.3 Ausgeglichene Bäume
6-33
AVL-Bäume
Definition: Ein binärer Suchbaum ist ein AVL-Baum, wenn für
jeden Knoten p des Baums gilt, dass sich die Höhe des linken
Teilbaums von der Höhe des rechten Teilbaums von p höchstens
um 1 unterscheidet.
G. M. Adelson-Velskii, E. M. Landis (1962)
6.3 Ausgeglichene Bäume
6-34
AVL-Bäume
Ein AVL-Baum:
Kein AVL-Baum:
11
11
5
17
2
7
6
6.3 Ausgeglichene Bäume
13
5
17
2
7
6
6-35
AVL-Bäume
Satz: Es sei ein beliebiger AVL-Baum der Höhe h mit n Knoten
gegeben. Dann gilt
h < 1.441 log2 (n + 2) − 0.328.
Die Klasse der AVL-Bäume erfüllt daher die
Ausgeglichenheitsbedingung. Wir werden gleich sehen, dass sie
auch die Rebalancierungsbedingung erfüllt. Damit gilt:
Satz: Die Klasse der AVL-Bäume ist ausgeglichen.
6.3 Ausgeglichene Bäume
6-36
Basisalgorithmen für AVL-Bäume
Da die Höhe h eines AVL-Baums durch
h < 1.441 log2 (n + 2) − 0.328
beschränkt ist, liegen die Laufzeiten der Algorithmen
◮
Suchen eines Knotens,
◮
Minimum, Maximum,
◮
Nachfolger und Vorgänger
in O (log n).
6.3 Ausgeglichene Bäume
6-37
Einfügen in AVL-Bäume
1. Wenn der einzufügende Schlüssel noch nicht im Baum
vorkommt, endet die Suche in einem Blatt. Der Schlüssel wird
dort wie bisher eingefügt.
2. Danach kann die AVL-Eigenschaft eines inneren Knotens k
verletzt sein.
3. Wir unterscheiden abhängig von der aufgetretenen Stelle die
folgenden Fälle:
1. Einfügen in linken Teilbaum des linken Kindes von k ,
2. Einfügen in rechten Teilbaum des linken Kindes von k ,
3. Einfügen in rechten Teilbaum des rechten Kindes von k ,
4. Einfügen in linken Teilbaum des rechten Kindes von k .
Die Fälle 1 und 3 sowie die Fälle 2 und 4 sind symmetrisch.
Die Rebalancierung erfolgt durch so genannte Rotationen bzw.
Doppelrotationen.
6.3 Ausgeglichene Bäume
6-38
Einfügen in AVL-Bäume
Fall 1: Einfügen in den linken Teilbaum des linken Kindes
y
x
-2
x
Rotation
-1
y
c
Einfügen
a
0
0
a
b
b
c
Rotiert wird hier das linke Kind nach rechts.
Fall 3 läuft spiegelbildlich ab.
6.3 Ausgeglichene Bäume
6-39
Einfügen in AVL-Bäume
Fall 2: Einfügen in den rechten Teilbaum des linken Kindes
z
x
-2
y
Doppelrotation
+1
x
0
0/-1
z
0/+1
y
a
d
Einfügen
b
a
b
c
d
c
Es hat eine Doppelrotation mit dem linken Kind nach links und
bzgl. des Vaters nach rechts stattgefunden.
Fall 4 läuft spiegelbildlich ab.
6.3 Ausgeglichene Bäume
6-40
Einfügen in AVL-Bäume
3
3
0
2
3
-1
1
2
3
2
-2
RR
-1
4
2
1
3
2
1
3
1
5
LR
2
1
3
2
1
1
4
5
6.3 Ausgeglichene Bäume
2
1
4
6
1
4
3
1
2
1
5
2
4
3
1
1
5
6
6-41
Einfügen in AVL-Bäume
LR
7
4
2
1
5
3
4
1
2
6
1
LR
1
5
3
4
2
6
2
1
1
6
3
5
7
7
9
4
2
1
8
1
6
3
5
4
1
2
1
7
1
6
3
5
2
7
9
9
-1
8
6.3 Ausgeglichene Bäume
6-42
Einfügen in AVL-Bäume
DR r/l
RR
LR
4
2
1
4
6
3
5
1
2
2
7
1
1
8
1
6
3
5
8
7
9
9
6.3 Ausgeglichene Bäume
6-43
Einfügen in AVL-Bäume
1. Fall Einfügen in linken Teilbaum des linken Kindes:
Rechtsrotation
2. Fall Einfügen in rechten Teilbaum des linken Kindes:
Doppelrotation, links/rechts
3. Fall Einfügen in rechten Teilbaum des rechten Kindes:
Linksrotation
4. Fall Einfügen in linken Teilbaum des rechten Kindes:
Doppelrotation, rechts/links
6.3 Ausgeglichene Bäume
6-44
Löschen in AVL-Bäumen
1. Der Schlüssel wird wie bisher gesucht und gelöscht.
2. Danach kann die AVL-Eigenschaft eines Knotens verletzt
sein.
3. Durch Rotationen kann die Ausgeglichenheit erreicht werden,
allerdings wird dadurch u. U. die AVL-Eigenschaft weiter oben
im Baum verletzt. Die Balance muss also ggf. rekursiv bis zur
Wurzel wiederhergestellt werden. Da für die Höhe h des
Baums h < 1.441 log2 (n + 2) gilt, bleibt die
Rebalancierungsbedingung erfüllt.
4
-1
4
Löschen von 5
2
1
6.3 Ausgeglichene Bäume
1
2
Rechtsrotation
5
3
-2
2
1
1
3
4
-1
3
6-45
Rot-Schwarz-Bäume
Ein Rot-Schwarz-Baum ist ein binärer Suchbaum, in dem jeder
Knoten über ein Zusatzbit zur Speicherung einer Farbe (rot oder
schwarz) verfügt.
Idee: Durch Einschränkungen bei der Färbung der Knoten auf den
Pfaden von der Wurzel zu einem Blatt wird sichergestellt, dass
jeder Pfad, der in der Wurzel beginnt, maximal doppelt so lang ist,
wie jeder andere solche Pfad.
Für nicht vorhandene Kind-Knoten, wird ein spezieller Null-Knoten
als Kind eingefügt.
8
4
12
2
6
1
Null
6.3 Ausgeglichene Bäume
3
Null
Null
10
5
Null
Null
7
Null
Null
14
9
Null
Null
11
Null
Null
13
Null
Null
15
Null
Null
Null
6-46
Rot-Schwarz-Bäume
Die Bedingungen an einen Rot-Schwarz-Baum lauten:
1. Jeder Knoten ist entweder rot oder schwarz.
2. Die Wurzel ist schwarz.
3. Jedes Blatt (Null-Knoten) ist schwarz.
4. Wenn ein Knoten rot ist, so sind beide Kinder schwarz.
5. Für jeden Knoten p gilt, dass alle Pfade vom Knoten zu einem
Blatt die selbe Anzahl schwarzer Knoten beinhalten.
8
4
12
2
6
1
Null
6.3 Ausgeglichene Bäume
3
Null
Null
10
5
Null
Null
7
Null
Null
14
9
Null
Null
11
Null
Null
13
Null
Null
15
Null
Null
Null
6-47
Rot-Schwarz-Bäume
Die gemäß Bedingung 5 eindeutig bestimmte Zahl wird
Schwarzhöhe bh (p ) eines Knotens p genannt. Hierbei wird p
selbst nicht mitgezählt.
bh(8) = 3
8
bh(12) = 2
4
12
2
6
10
14
bh(11) = 1
1
Null
6.3 Ausgeglichene Bäume
3
Null
Null
5
Null
Null
7
Null
Null
9
Null
Null
11
Null
Null
13
Null
Null
15
Null
Null
Null
6-48
Rot-Schwarz-Bäume
Satz: Es sei ein beliebiger Rot-Schwarz-Baum der Höhe h mit n
Knoten gegeben. Dann gilt
h < 2 log2 (n + 1).
Die Klasse der Rot-Schwarz-Bäume erfüllt daher die
Ausgeglichenheitsbedingung. Wir werden gleich sehen, dass sie
auch die Rebalancierungsbedingung erfüllt. Damit gilt:
Satz: Die Klasse der Rot-Schwarz-Bäume ist ausgeglichen.
6.3 Ausgeglichene Bäume
6-49
Basisalgorithmen für Rot-Schwarz-Bäume
Da die Höhe h eines Rot-Schwarz-Baums durch
h < 2 log2 (n + 1)
beschränkt ist, liegen die Laufzeiten der Algorithmen
◮
Suchen eines Knotens,
◮
Minimum, Maximum,
◮
Nachfolger und Vorgänger
in O (log n).
6.3 Ausgeglichene Bäume
6-50
Einfügen in Rot-Schwarz-Bäume
1. Einfügen eines Schlüssels mit bisherigem Algorithmus.
2. Der neue Knoten wird rot und erhält zwei Null-Knoten als
Kinder.
3. Danach kann die Rot-Schwarz-Eigenschaft verletzt sein:
3.1 Jeder Knoten ist entweder rot oder schwarz. Wird nicht
verletzt.
3.2 Die Wurzel ist schwarz. Wird verletzt, falls in den leeren Baum
eingefügt wird. Der neue Knoten wird dann schwarz gefärbt.
3.3 Jedes Blatt ist schwarz. Wird nicht verletzt.
3.4 Wenn ein Knoten rot ist, so sind beide Kinder schwarz. Nicht
durch k verletzt, da beide Kinder schwarze Null-Knoten sind.
Die Eigenschaft wird verletzt, falls k als Kind eines roten
Vaterknotens eingefügt wird.
3.5 Für jeden Knoten gilt, dass alle Pfade vom Knoten zu einem
Blatt die selbe Anzahl schwarzer Knoten beinhalten. Da nur
ein roter Knoten hinzukommt, wird diese Eigenschaft nicht
verletzt.
6.3 Ausgeglichene Bäume
6-51
Einfügen in Rot-Schwarz-Bäume
Maßnahmen, die die Rot-Schwarz-Eigenschaft 4 wiederherstellen:
◮
Links-, Rechts- und Doppel-Rotationen zwecks
Höhenausgleich. Dabei gibt die Einfärbung der Knoten
Aufschluss über die notwendigen Rotationen.
◮
Korrektur der Einfärbung der falsch eingefärbten Knoten.
Die Einfärbung wird in Richtung der Wurzel korrigiert, wodurch ein
Dienst zum Zugriff auf den Vaterknoten eines Knotens nötig wird.
Dieser sei im Folgenden mit parent (k ) beschrieben. Sechs Fälle
sind zu unterscheiden:
1. parent (k ) ist linkes Kind von parent (parent (k ))
1.1 Der Onkel von k ist rot.
1.2 Der Onkel von k ist schwarz und k ist rechtes Kind.
1.3 Der Onkel von k ist schwarz und k ist linkes Kind.
2. parent (k ) ist rechtes Kind von parent (parent (k )): analog.
6.3 Ausgeglichene Bäume
6-52
Einfügen in Rot-Schwarz-Bäume
Fall 1.1: Der Onkel von k ist rot:
y
y
Umfärben
w
z
x
k
z
x
a
d
b
w
c
e
k
a
d
b
e
c
Knoten y kann ebenfalls wieder Kind eines roten Knotens sein
(erneute Verletzung der Eigenschaft 4). In diesem Fall wird für y
rekursiv die Eigenschaft 4 wiederhergestellt. Da die Wurzel
schwarz ist, terminiert das Verfahren.
Der Fall, dass k linkes Kind von parent (k ) ist, wird analog
behandelt.
6.3 Ausgeglichene Bäume
6-53
Einfügen in Rot-Schwarz-Bäume
Fall 1.2: Der Onkel von k ist schwarz und k ist rechtes Kind:
y
y
Linksrotation
w
z
x
k
z
w
a
d
b
x
c
e
c
a
d
e
b
Durch Linksrotation entsteht Fall 1.3.
6.3 Ausgeglichene Bäume
6-54
Einfügen in Rot-Schwarz-Bäume
Fall 1.3: Der Onkel von k ist schwarz und k ist linkes Kind:
y
x
Rechsrotation
Umfärben
x
w
z
k
z
c
a
y
w
b
d
e
a
b
c
d
e
Es wird eine Rechtsrotation und eine Umfärbung durchgeführt.
6.3 Ausgeglichene Bäume
6-55
B-Bäume
◮
Der Zugriff auf den Primärspeicher (RAM, Hauptspeicher) ist
bezüglich der Zugriffszeit „billig“, wohingegen der Zugriff auf
den Sekundärspeicher (Festplatte) „teuer“ ist, vor allem dann,
wenn das Auslesen der Daten eine Änderung der Position des
Lesekopfes nötig macht oder der Beginn des einzulesenden
Bereiches abgewartet werden muss.
◮
Daher ist es sinnvoll, zusammenhängende Daten, auf die
komplett zugegriffen wird, möglichst beieinander liegend zu
speichern.
◮
Diese Idee kann man auf Suchbäume übertragen.
6.3 Ausgeglichene Bäume
6-56
B-Bäume
B-Bäume sind ausgeglichene geordnete k -näre Suchbäume,
deren Knoten
◮
◮
maximal k − 1 Schlüssel tragen können und
auf maximal k Kindknoten verweisen.
B-Bäume sind nicht binär, das B steht für „balanced“.
10
13
6.3 Ausgeglichene Bäume
14
17
20
30
23
24
27
6-57
B-Bäume
Ein 2t -närer Baum T heißt B-Baum der Ordnung t , t ≥ 2, wenn er
die folgenden Eigenschaften erfüllt:
1. Jeder Knoten x besitzt die folgenden Felder bzw. Funktionen:
◮ n (x ) ist die Anzahl der in Knoten x gespeicherten Schlüssel
◮ die n (x ) Schlüssel sind in aufsteigender Weise geordnet:
key1 (x ) ≤ key2 (x ) ≤ · · · ≤ keyn(x ) (x )
◮ leaf (x ) ist eine boolesche Funktion, die angibt, ob x ein Blatt
ist.
2. Jeder innere Knoten x trägt n(x ) + 1 Zeiger
c1 (x ), c2 (x ), . . . , cn(x )+1 (x ) auf seine Kindknoten.
3. Die Schlüssel keyi (x ) unterteilen die in den Teilbäumen
gespeicherten Werte. Sei treeKeys (y ) die Menge aller in
einem B-Baum mit Wurzel y gespeicherten Schlüssel, so gilt
für 1 ≤ i ≤ n(x ): ∀vi ∈ treeKeys (ci (x )),
vi +1 ∈ treeKeys (ci +1 (x )) : vi ≤ keyi (x ) ≤ vi +1 .
6.3 Ausgeglichene Bäume
6-58
B-Bäume
4. Jedes Blatt hat das gleiche Niveau. Es entspricht der Höhe h
des Baums.
5. Die Ordnung t ≥ 2 legt die obere und die untere Grenze der
Anzahl der Schlüssel und der Kindknoten eines Knotens fest:
◮ Jeder Knoten mit Ausnahme der Wurzel hat wenigstens t − 1
◮
◮
◮
Schlüssel.
Jeder innere Knoten mit Ausnahme der Wurzel hat wenigstens
t Kindknoten.
Ist der Baum nicht leer, so trägt die Wurzel mindestens einen
Schlüssel.
Jeder Knoten trägt maximal 2t − 1 Schlüssel. Damit hat jeder
innere Knoten höchstens 2t Kinder. Ein Knoten heißt voll,
wenn er genau 2t − 1 Schlüssel trägt.
Es gilt: t − 1 ≤ n(x ) ≤ 2t − 1 für alle Knoten mit Ausnahme der
Wurzel und t ≤ #Kinder von x ≤ 2t für alle inneren Knoten mit
Ausnahme der Wurzel.
6.3 Ausgeglichene Bäume
6-59
B-Bäume
Beispiel: B-Baum der Ordnung 3
Wurzel
50
10
20
30
40
k
45
55
59
70
...
3
◮
◮
7
8
9
Blatt
13
14
17
Blatt
75
...
65
66
Blatt
Für alle Knoten x mit Ausnahme der Wurzel gilt:
3 − 1 = 2 ≤ n(x ) ≤ 2 · 3 − 1 = 5.
Für alle inneren Knoten mit Ausnahme der Wurzel gilt:
3 ≤ # Kinder von x ≤ 2 · 3 = 6
◮
Der Knoten k ist voll, sein rechter Bruder nicht.
◮
Es gibt insgesamt 11 Blätter, die alle das Niveau 2 haben.
6.3 Ausgeglichene Bäume
6-60
B-Bäume
◮
Alle Pfade von der Wurzel bis zu den Blättern sind in einem
B-Baum gleich lang.
◮
Typischerweise werden B-Bäume hoher Ordnung benutzt. Die
Knoten enthalten dadurch sehr viele Werte, die Höhe des
Baumes ist aber gering.
◮
B-Bäume werden im Zusammenhang mit Datenbanken zum
Beispiel für Indizierungen verwendet.
◮
Die Anzahl der Knoten eines Niveaus nimmt bei einem
vollständigen B-Baum der Ordnung t exponentiell zur Basis 2t
zu: Jeder Knoten hat 2t Kinder, auf Niveau n befinden sich
(2t )n Knoten.
◮
Die Ordnung t eines B-Baumes wird auch als minimaler Grad
bezeichnet.
6.3 Ausgeglichene Bäume
6-61
B-Bäume
Wie für die binären Suchbäume ist die Laufzeit (und damit die
Anzahl der Festplattenzugriffe) für die meisten
B-Baum-Operationen abhängig von der Höhe des Baums.
Satz: Ist n ≥ 1 die Anzahl der Schlüssel eines B-Baumes der Höhe
h der Ordnung t , so gilt
h ≤ logt
6.3 Ausgeglichene Bäume
!
n+1
.
2
6-62
Suchen in B-Bäumen
Suche in B-Bäumen kombiniert die Suche in binären Suchbäumen
mit der Suche in Listen. Jeder Knoten muss dazu durch einen
Festplattenzugriff erst in den Speicher geladen werden, bevor er
bearbeitet werden kann.
Um einen Schlüssel k zu suchen:
1. Lies den Wurzelknoten x ein.
2. Vergleiche in x beginnend mit i = 1 jeden Schlüssel keyi (k )
mit k bis ein Wert keyi (x ) ≥ k oder i = n(x ) ist.
2.1 Ist k = keyi (x ), dann liefere Knoten x und Index i zurück und
beende die Suche.
2.2 Ist k , keyi (x ) und x ein Blatt, so ist der Schlüssel nicht
enthalten, und die Suche ist fehlgeschlagen.
2.3 Ist keyi (x ) > k bzw. keyi (x ) < k und i = n(x ), so lies Knoten
ci (x ) bzw. ci +1 (x ) ein und fahre mit Schritt 2 fort.
6.3 Ausgeglichene Bäume
6-63
Suchen in B-Bäumen
Suchen des Buchstabens Q in B-Bäumen der Ordnung 3:
Q>N
N
C
A
B
D
E
F
K
H
S
Q<S
L
M
O
Q
R
W
T
V
X
Y
Z
X
Y
Z
Q=Q
Q>O
Q>N
N
C
A
B
D
E
F
K
H
Suche erfolgreich
S
Q<S
L
M
O
P
R
W
T
V
Q<R
Q>O
Q>P
6.3 Ausgeglichene Bäume
Suche fehlgeschlagen
6-64
Suchen in B-Bäumen
1. Die Suche innerhalb eines Knotens erfolgt linear und ist
beendet, wenn ein Schlüssel größer oder gleich dem
gesuchten Schlüssel ist oder alle n(x ) Werte des Knotens
betrachtet worden sind. In einem B-Baum der Ordnung t ist
n(x ) ≤ 2t . Daher liegt die Laufzeit dieser lokalen Suche in
O (t ).
2. Wird der Schlüssel in einem inneren Knoten nicht gefunden,
so wird der nächste Knoten in Richtung der Blätter bearbeitet.
Die Anzahl der besuchten Knoten (die Anzahl der
Festplattenzugriffe) ist damit abhängig von der Höhe h des
Baumes. Sie liegt nach obigem Satz in Θ(h ) = Θ(logt n).
3. Die Laufzeit des gesamten Algorithmus liegt also in
O (t · h ) = O (t logt n).
6.3 Ausgeglichene Bäume
6-65
Einfügen in B-Bäume
Das Einfügen eines Wertes in einen B-Baum der Ordnung t ist
komplizierter als bei binären Suchbäumen:
◮
Suche analog zum binären Suchbaum zuerst ein Blatt, in dem
der Wert gespeichert werden kann. Sollte das Blatt vor dem
Einfügen bereits voll gewesen sein, so verstößt der Baum
danach gegen die B-Baum-Definition.
◮
Der übervolle Knoten wird in zwei Knoten am Median des
ursprünglichen Knotens aufgeteilt.
◮
Beispiel: B-Baum der Ordnung 3
S
Einfügen von W
C
6.3 Ausgeglichene Bäume
K
S
X
Y
C
K
W
X
Y
6-66
Einfügen in B-Bäume
◮
Der neue Vaterknoten muss in den ursprünglichen
Vaterknoten integriert werden, wodurch wiederum die
B-Baum-Eigenschaft verletzt sein kann.
◮
In Richtung Wurzel ist demnach solange jeder entstehende
übervolle Knoten aufzuteilen, bis spätestens ein neuer Vater
die neue Wurzel des Baumes bildet.
◮
Das beschriebene Verfahren durchläuft den Baum ggf.
zweimal: Erst wird der Baum in Richtung eines Blattes
durchsucht, der Knoten eingefügt und dann in Richtung der
Wurzel korrigiert.
◮
Ein effizienteres Verfahren, dass den Baum nur einmal
durchläuft, teilt auf dem Suchpfad in Richtung des Zielblattes
vorsorglich jeden vollen Knoten auf und fügt zum Schluss den
Wert in einen Knoten ein, der noch nicht voll ist
(One-Pass-Verfahren).
6.3 Ausgeglichene Bäume
6-67
Einfügen in B-Bäume
Beispiel: B-Baum der Ordnung 3
L
C
F
K
Q
H
S
F
K
L
W
C
F
Q
M
H
S
L
Q
V
K
C
K
T
S
R
C
F
H
V
W
Q
S
T
V
N
K
T
L
C
F
H
L
S
M
N
Q
T
V
W
R
T
V
W
P
K
C
6.3 Ausgeglichene Bäume
F
H
L
M
N
S
P
Q
R
6-68
Einfügen in B-Bäume
A
B
X
Y
K
A
B
C
F
H
L
M
N
S
Q
P
R
T
V
W
X
Y
D
C
A
B
D
F
H
L
K
N
M
S
P
Q
S
W
P
Q
R
T
V
W
X
Y
Z
C
A
B
6.3 Ausgeglichene Bäume
D
F
H
L
K
M
N
R
T
V
X
Y
Z
6-69
Einfügen in B-Bäume
E
C
A
B
D
E
F
H
L
K
N
M
S
W
P
Q
ohne One-Pass-Verfahren
R
T
V
X
Y
Z
E
mit One-Pass-Verfahren
N
A
B
D
6.3 Ausgeglichene Bäume
C
K
E
F
S
H
L
M
P
Q
R
W
T
V
X
Y
Z
6-70
Löschen in B-Bäumen
Um einen Schlüssel in einem B-Baum der Ordnung t zu löschen:
Suche den Knoten x , in dem der Schlüssel k = keyi (x ) gelöscht
werden soll. Wird der Schlüssel aus dem betreffenden Knoten x
entfernt, so können mehrere Fälle auftreten, von denen wir uns nur
drei beispielhaft ansehen:
1. x ist ein Blatt und trägt mehr als t − 1 Schlüssel. Dann kann
der Schlüssel einfach gelöscht werden.
6.3 Ausgeglichene Bäume
6-71
Löschen in B-Bäumen
2. x ist ein Blatt, trägt die minimale Anzahl von t − 1 Schlüsseln
und ein Bruder b von x trägt mindestens t Schlüssel. Dann
findet vor dem Löschen eine Rotation des kleinsten bzw.
größten Schlüssels von b mit dem Schlüssel des
Vaterknotens statt, dessen Teilbäume b und x sind.
K
N
S
K
P
S
Löschen von M
F
H
6.3 Ausgeglichene Bäume
L
M
P
Q
R
T
V
F
H
L
N
Q
R
T
V
6-72
Löschen in B-Bäumen
3. x ist ein Blatt und trägt die minimale Anzahl von t − 1
Schlüsseln. Gleiches gilt für beide Brüder. Dann findet eine
Verschmelzung von x und einem Bruder b statt, wobei der
Schlüssel des Vaterknotens, dessen Teilbäume b und x sind,
in der Mitte des neuen Knoten zwischen den Werten von x
und b gespeichert wird. Dabei kann der Vaterknoten die
kritische Größe t − 1 Schlüssel unterschreiten. Ggf. muss also
rekursiv nach oben verschmolzen werden
K
N
S
N
S
Löschen von H
F
H
6.3 Ausgeglichene Bäume
L
M
P
Q
R
T
V
F
K
L
M
P
Q
R
T
V
6-73
Löschen in B-Bäumen
◮
Die anderen Fälle werden ähnlich behandelt. Es erfolgt vor
dem Löschen evtl. ein Zusammenfügen oder ein Aufspalten
einzelner Knoten. Dabei muss ggf. rekursiv vorgegangen
werden.
◮
Man kann zeigen, dass die Laufzeit der beiden Algorithmen
zum Einfügen und Löschen ebenfalls in O (t logt n) liegt.
◮
Einzelheiten kann und sollte man in der Literatur (zum
Beispiel Cormen et al., S. 439–457) nachlesen.
6.3 Ausgeglichene Bäume
6-74
Überblick
6.4 Heapsort
◮
Heapsort ist ein Sortieralgorithmus, der ein gegebenes Feld
in-place sortiert.
◮
Heapsort verwendet eine auf Binärbäumen basierende
Datenstruktur, den binären Heap.
◮
Das zu sortierende Feld wird dabei als ausgeglichener binärer
Baum aufgefasst, der bis auf die Ebene der Blätter vollständig
gefüllt ist.
◮
Die Ebene der Blätter ist von links bis zu einem Endpunkt
gefüllt.
◮
In dem Binärbaum gilt nicht die Suchbaumeigenschaft,
sondern die Heap-Eigenschaft.
6-75
Heaps
Ein binärer Max-Heap ist ein Binärbaum, bei dem der Schlüssel
eines Vaterknotens größer oder gleich den Schlüsseln seiner
Kindknoten ist. Die Schlüssel der Kinder stehen untereinander in
keiner Beziehung.
Beim Heapsort wird das zu sortierende Feld a mit der Indexmenge
1 . . . n im Bereich 1 . . . h , h ≤ n, (Heap-Size) als binärer Heap
interpretiert:
◮
a [1] ist die Wurzel des Baumes.
◮
left (k ) = 2k ist der Index des linken Kindes des Knotens k .
◮
right (k ) = 2k + 1 ist der Index des rechten Kindes des
Knotens k .
◮
6.4 Heapsort
parent (k ) = ⌊ k2 ⌋ ist der Index des Vaters des Knotens k .
◮
a .size = n ist die Gesamtlänge des Felds.
◮
a .heapSize = h ist die Länge des Heaps.
6-76
Heaps
In einem Max-Heap gilt die Max-Heap-Eigenschaft:
∀k ∈ {2, . . . , h }.a [parent (k )] ≥ a [k ]
D. h., die Schlüssel der Kinder eines Knotens sind kleiner oder
gleich dem Schlüssel des Vaterknotens.
n=9
9
h=6
9 6 8 1 5 7 2 0 3
6.4 Heapsort
6
1
4
1
2
5
8
5
7
3
6
6-77
Heapsort
Der Algorithmus benötigt 3 Methoden:
6.4 Heapsort
◮
Max-Heapify: stellt die Max-Heap-Eigenschaft für einen
Teilbaum sicher.
◮
Build-Max-Heap: konstruiert ausgehend von einem
unsortierten Feld einen Max-Heap.
◮
Heapsort: sortiert ein ungeordnetes Feld in-place.
6-78
Max-Heapify
6.4 Heapsort
◮
Max-Heapify bekommt eine Referenz auf das Feld a
übergeben, in dem ggf. die Heap-Eigenschaft an nur einer
Stelle verletzt ist.
◮
Außerdem einen Index k , der denjenigen Knoten in a angibt,
der die Heap-Eigenschaft verletzt. Die Teilbäume unterhalb
von k verletzen die Heap-Eigenschaft nach Voraussetzung
nicht.
◮
Max-Heapify stellt die Heap-Eigenschaft her.
6-79
Max-Heapify
17 1
6
11 4
17 1
10 3
2
4 5
1 6
11 2
7 7
6
5 8 8 9 2 10 3 11
10 3
4 5
4
1 6
7 7
5 8 8 9 2 10 3 11
17 1
11 2
8 4
10 3
4 5
1 6
7 7
5 8 6 9 2 10 3 11
6.4 Heapsort
6-80
Max-Heapify
proc Max-Heapify(a: <Referenz auf T[]>; k: int) begin
var max: int;
max ← k;
if left(k) ≤ a.heapSize &&
a[left(k)] > a[max] then
max ← left(k);
fi;
if right(k) ≤ a.heapSize &&
a[right(k)] > a[max] then
max ← right(k);
fi;
if k , max then
swap(a[k], a[max]);
Max-Heapify(a, max);
fi;
end
6.4 Heapsort
6-81
Max-Heapify
6.4 Heapsort
◮
Alle Operationen in Max-Heapify bis auf den rekursiven Aufruf
sind in O (1) implementierbar.
◮
Falls ein rekursiver Aufruf erfolgt, dann nur auf Knoten, die
unterhalb von k liegen.
◮
Die Aufruffolge ist daher durch die Höhe des von k
aufgespannten Teilbaums nach oben begrenzt.
◮
Da auch der Teilbaum vollständig ist, liegt die Laufzeit von
Max-Heapify innerhalb von O (log n).
6-82
Build-Max-Heap
◮
Build-Max-Heap erhält als Eingabe eine Referenz auf ein
unsortiertes Feld und stellt sicher, dass im gesamten Feld die
Max-Heap-Eigenschaft gilt.
◮
Dies geschieht, indem Max-Heapify auf alle Knoten
angewendet wird.
◮
Da Max-Heapify erwartet, dass die Teilbäume unterhalb eines
Knotens die Heap-Eigenschaft erfüllen, muss vom Ende des
Felds begonnen werden.
◮
6.4 Heapsort
Alle inneren Knoten des Heaps liegen im Bereich ⌊ n2 ⌋ . . . 1. Auf
diese Knoten muss also Max-Heapify angewendet werden.
6-83
Build-Max-Heap
proc Build-Max-Heap(a: <Referenz auf T[]>) begin
var i: int;
a.heapSize ← a.length;
for i ← ⌊a.length / 2⌋ downto 1 do
Max-Heapify(a, i);
od;
end
6.4 Heapsort
6-84
Build-Max-Heap
12 1
12 1
1. Iteration
6 2
11 4
5 8
1 3
3 5
8 9
17 6
6 2
10 7
11 4
2 10 4 11
5 8
1 3
4 5
8 9
10 7
2 10 3 11
12 1
12 1
2. Iteration
3. Iteration
6 2
11 4
5 8
6.4 Heapsort
17 6
8 9
1 3
4 5
2 10 3 11
17 6
6 2
10 7
11 4
5 8
8 9
17 3
4 5
1 6
10 7
2 10 3 11
6-85
Build-Max-Heap
12 1
12 1
4. Iteration
11 2
6 4
5 8
17 3
4 5
8 9
1 6
11 2
10 7
2 10 3 11
8 4
5 8
6 9
17 3
4 5
1 6
10 7
2 10 3 11
17 1
5. Iteration
11 2
8 4
5 8
6.4 Heapsort
6 9
12 6 1 11 3 17 10 5 8 2 4
12 3
4 5
2 10 3 11
1 6
10 7
17 11 12 8 4 1 10 5 6 2 3
6-86
Build-Max-Heap
Der Aufruf von Max-Heapify liegt jeweils in O (log n), Max-Heapify
wird O (n)-mal aufgerufen, daher ergibt sich eine Laufzeit
innerhalb von O (n log n).
Allerdings ist eine genauere Abschätzung möglich: Es gilt, dass
ein Heap mit n Elementen die Höhe ⌊ld (n)⌋ hat und dass
höchstens ⌈ 2hn+1 ⌉ Knoten die Höhe h haben.
∞
X
h
h =0
⌊ld
(h )⌋ X
h =0
n
2h +1
2h
=2
 ⌊ld (h )⌋ 
 ∞

 X h 
 X h 


 = O (n)
 = O n
O (h ) = O n


2h 
2h 
h =0
h =0
Build-Max-Heap hat also eine lineare Laufzeit.
6.4 Heapsort
6-87
Heapsort
Eingabe für Heapsort ist ein Referenzparameter auf ein
unsortiertes Feld a , Ausgabe ist das sortierte Feld a . Der
Algorithmus arbeitet folgendermaßen:
1. Konstruiere einen Max-Heap.
2. Vertausche das erste Element (Wurzel, größtes Element) und
das letzte Element (Blatt ganz rechts) des Heaps.
3. Reduziere den Max-Heap um das Blatt ganz rechts.
4. Wende Max-Heapify auf die Wurzel an.
5. Solange Blätter vorhanden und nicht gleich der Wurzel sind,
fahre mit Schritt 2 fort.
6.4 Heapsort
6-88
Heapsort
proc Heapsort(a: <Referenz auf T[]>) begin
var i: int;
Build-Max-Heap(a);
for i ← a.length downto 2 do
swap(a[1], a[i]);
a.heapSize ← a.heapSize - 1;
Max-Heapify(a, 1);
od;
end
Die Laufzeit von Heapsort liegt in O (n log n).
6.4 Heapsort
6-89
Heapsort
12 1
Heap
6 2
12 6 1 11 3 17 10 5 8 2 4
11 4
5 8
1 3
3 5
8 9
10 7
2 10 4 11
17 1
3 1
swap
MaxHeapify
11 2
8 4
5 8
6.4 Heapsort
17 6
6 9
12 3
4 5
2 10 3 11
1 6
11 2
10 7
8 4
5 8
6 9
12 3
4 5
1 6
10 7
2 10 17 11
6-90
Heapsort
12 1
2 1
swap
MaxHeapify
11 2
8 4
5 8
10 3
4 5
6 9
1 6
11 2
3 7
2 10 17 11
8 4
5 8
10 3
4 5
3 7
6 9 12 10 17 11
11 1
10 1
MaxHeapify
swap, MaxHeapify
8 2
6 4
5 8
6.4 Heapsort
1 6
10 3
4 5
2 9 12 10 17 11
1 6
8 2
3 7
6 4
3 3
4 5
1 6
2 7
5 8 11 9 12 10 17 11
6-91
Heapsort
8 1
6 1
MaxHeapify
swap,MaxHeapify
6 2
5 4
3 3
4 5
1 6
5 2
2 7
2 4
10 8 11 9 12 10 17 11
3 3
4 5
1 6
8 7
10 8 11 9 12 10 17 11
1 1
Array
···
2 2
4 4
1 2 3 4 5 6 8 10 11 12 17
3 3
5 5
6 6
8 7
10 8 11 9 12 10 17 11
6.4 Heapsort
6-92
Prioritätswarteschlangen
Heaps können nicht nur zum Sortieren benutzt werden, sondern
auch für weitere Anwendungen. Eine wollen wir jetzt betrachten.
Eine Max-Prioritätswarteschlange ist eine Datenstruktur zur
Speicherung einer Menge S , deren Elementen ein Schlüssel
(Priorität) zugeordnet ist. Prioritätswarteschlangen besitzen die
folgenden Operationen:
◮
maximum(S) gibt das Element von S mit dem maximalen
Schlüssel zurück.
◮
extract-max(S) entfernt aus S das maximale Element.
◮
increase-key(S,x,k) erhöht den Schlüssel von x auf k .
◮
insert(S,x) fügt x in S ein.
Max-Prioritätswarteschlangen können effizient durch Max-Heaps
implementiert werden.
6.4 Heapsort
6-93
Prioritätswarteschlangen
proc maximum(S) begin
return S[1];
end
proc extract-max(S) begin
if heap-groesse(S) < 1 then error fi;
max ← S[1];
S[1] ← S[heap-groesse(S)];
heap-groesse(S) ← heap-groesse(S)-1;
Max-Heapify(S,1);
return max;
end
6.4 Heapsort
6-94
Prioritätswarteschlangen
proc increase-key(S,x,k) begin
if k < S[x] then error fi;
S[x] ← k;
while x > 1 ∧ S[vater(x)] < S[x] do
swap (S[x],S[vater(x)]);
x ← vater(x);
od;
end
proc insert(S,x) begin
heap-groesse(S) ← heap-groesse(S)+1;
S[heap-groesse(S)] ← −∞;
increase-key(S,heap-groesse(S),k);
end
6.4 Heapsort
6-95
Prioritätswarteschlangen
6.4 Heapsort
◮
Die Laufzeiten der vier Operationen für
Prioritätswarteschlangen der Größe n liegen in O (log n).
◮
Analog zu Max-Heaps und Max-Prioritätswarteschlangen
lassen sich Min-Heaps und Min-Prioritätswarteschlangen
definieren.
6-96
Herunterladen