• Keine Ergebnisse gefunden

Listen und Bäume

N/A
N/A
Protected

Academic year: 2022

Aktie "Listen und Bäume"

Copied!
98
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Algorithmen und Datenstrukturen

Werner Struckmann

Wintersemester 2005/06

(2)

6. Bäume

6.1 Bäume

6.2 Binäre Suchbäume 6.3 Ausgeglichene Bäume 6.4 Heapsort

(3)

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:

Ausdrücke,

Speicherung von Mengen,

hierarchisch strukturierte Daten, z. B. Dateibäume,

Sortieralgorithmen.

(4)

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.

Jeder Knoten kann einen Schlüssel und weitere Informationen speichern.

11

5 17

2 7 13 22

6 16

(5)

Grundbegriffe

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 13 22

6 16

(6)

Pfade

Ein Pfad in einem Baum ist eine Folge aufeinanderfolgender

Knoten. Die Anzahl der Knoten eines Pfades minus 1 heißt

dessen Länge.

In jedem Baum gibt es von der Wurzel zu einem beliebigen Knoten genau einen Pfad.

Jeder Baum ist

zusammenhängend.

Jeder Baum ist zyklenlos.

11

5 17

2 7 13 22

6 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.

(7)

Niveau und Höhe

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

5 17

2 7 13 22

6 16

Niveau 0

Niveau 1

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.

(8)

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:

Binäre Suchbäume sind geordnete Binärbäume.

(9)

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 13 22

6 16

Höhe des Baumes: 3 Max. Anzahl Knoten: 15 Max. Anzahl Blätter: 8 Max. Anzahl innerer Knoten: 7

(10)

Binärbäume als abstrakter Datentyp

type

BinTree(T)

import

Bool

operators

empty :→ BinTree

bin : BinTree × T × BinTreeBinTree left : BinTree → BinTree

right : BinTree → BinTree value : BinTree → T

empty? : BinTree → Bool

axioms

tT,∀x,yBinTree left(bin(x, t, y)) = x

right(bin(x,t,y)) = y value(bin(x,t,y)) = t empty?(empty) = true

(11)

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: 2h1

Maximalzahl der Blätter: 2h

h + 1 ≤ n2h+11

Folgerung:

log2(n + 1) − 1hn1 Das heißt: h liegt zwischen Θ(log(n)) und Θ(n).

(12)

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.

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.

(13)

Tiefendurchlauf

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

5 17

2 7 13 22

6 16

1. 2.

(14)

Inorder-Durchlauf

proc inorder(x) begin if x

,

nil then

inorder(left(x));

bearbeite(x);

inorder(right(x));

fi;

end

(15)

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

(16)

Breitendurchlauf

Levelorder-Durchlauf:

11,5, 17,2,7, 13,22,6, 16

11

5 17

2 7 13 22

6 16

1.

2.

3.

(17)

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

(18)

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.

11

5 17

2 7 13 22

6 16

root

Person 11

Person 5 Person 17

Person 2

Person 7 Person 13

Person 22

Person 6 Person 16

(19)

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

5 17

2 7 13 22

6 16

<11 >11

Die Nutzdaten werden im Folgenden nicht betrachtet.

(20)

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.

(21)

Suchen eines Knotens (rekursiv)

11

5 17

2 7 13 22

6 16

7<11

7>5

7

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

(22)

Suchen eines Knotens (iterativ)

11

5 17

2 7 13 22

6 16

7<11

7>5

7

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;

(23)

Minimum und Maximum

11

5 17

2 7 13 22

6 16

2

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

(24)

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.

Nachfolger von 7:

11

5 17

2 7 13 22

6 16

11

(25)

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.

(26)

Einfügen eines Knotens

Einfügen des Schlüssels 12:

11

5 17

2 7 13 22

6 16

12 > 11

12 < 17

12 12 < 13

(27)

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

(28)

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.

11

5 17

2 7 13 22

6 16

Der Knoten hat ein Kind:

Er wird ausgeschnitten.

11

5 17

2 7 13 22

(29)

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 13 22

6 16

(30)

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;

(31)

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.

(32)

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)).

(33)

Gestaltsanalyse

Satz: Für die Anzahl bn der strukturell verschiedenen Binärbäume gilt:

bn = 1 n + 1

2n n

!

=





1, falls n = 0,

Pn1

k=0 bkbn1k, falls n > 0. Annahmen:

1. Die Schlüssel sind paarweise verschieden.

2. Jeder der bn Binärbäume ist gleichwahrscheinlich.

Satz: Der mittlere Abstand eines Knotens von der Wurzel eines Binärbaums mit n Knoten ist O √

n .

Beweis: s. Ottmann/Widmayer, S. 271–275.

(34)

Ausgeglichene Bäume

Höhe des Binärbaums: h

Anzahl der Knoten: n

Es gilt: log2(n + 1) − 1hn1.

Binäre Suchbäume können zu Listen „entarten“:

1

2

3

5

8

13

21

(35)

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 ∀TM : 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.

(36)

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)

(37)

AVL-Bäume

Ein AVL-Baum:

11

5 17

2 7 13

6

Kein AVL-Baum:

11

5 17

2 7

6

(38)

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.

(39)

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).

(40)

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.

(41)

Einfügen in AVL-Bäume

Fall 1: Einfügen in den linken Teilbaum des linken Kindes

Einf¨ugen

y -2

x -1

a b

c

Rotation

x 0

y 0

a

b c

Rotiert wird hier das linke Kind nach rechts.

Fall 3 läuft spiegelbildlich ab.

(42)

Einfügen in AVL-Bäume

Fall 2: Einfügen in den rechten Teilbaum des linken Kindes

Einf¨ugen

z -2

x +1

y a

b c

d

Doppelrotation

y 0

x 0/-1

z 0/+1

a b c d

Es hat eine Doppelrotation mit dem linken Kind nach links und bzgl. des Vaters nach rechts stattgefunden.

(43)

Einfügen in AVL-Bäume

3 3 0 2

3 -1 2

1 3 -2

2 -1 1

RR 2

1 3

4 2 1

1 3 1

4

5 2

1 3 2

4 1

5

LR 2 1

1 4

3 5

6 2 2

1 4 1

3 5 1

6

(44)

Einfügen in AVL-Bäume

LR 4

2 5 1

1 3 6

7 4 1

2 5 2

1 3 6 1

7

LR 4

2 6

1 3 5 7

9 4 1

2 6 1

1 3 5 7 1

9

8 4

2 6

1 3 5 7 2

9 -1 8

(45)

Einfügen in AVL-Bäume

DR r/l

RR 4

2 6

1 3 5 7 2

8 1

9

LR 4 1

2 6 1

1 3 5 8

7 9

(46)

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

(47)

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

2 5

1 3

L¨oschen von 5

4 -2

2

1 3

Rechtsrotation

2 1

1 4 -1

3

(48)

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 10 14

1 3 5 7 9 11 13 15

(49)

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 10 14

1 3 5 7 9 11 13 15

Null Null Null Null Null Null Null Null Null Null Null Null Null Null Null Null

(50)

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.

8

bh(8) = 3

4 12

bh(12) = 2

2 6 10 14

1 3 5 7 9 11

bh(11) = 1

13 15

Null Null Null Null Null Null Null Null Null Null Null Null Null Null Null Null

(51)

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.

(52)

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).

(53)

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.

(54)

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.

(55)

Einfügen in Rot-Schwarz-Bäume

Fall 1.1: Der Onkel von k ist rot:

y

w z

x k

a

b c

d e

Umf¨arben

y

w z

x k

a

b c

d e

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.

(56)

Einfügen in Rot-Schwarz-Bäume

Fall 1.2: Der Onkel von k ist schwarz und k ist rechtes Kind:

y

w z

x k

a

b c

d e

Linksrotation

y

x z

w

a b

c d e

Durch Linksrotation entsteht Fall 1.3.

(57)

Einfügen in Rot-Schwarz-Bäume

Fall 1.3: Der Onkel von k ist schwarz und k ist linkes Kind:

y

x z

w k

a b

c d e

Rechsrotation Umf¨arben

x

w y

z

a b c

d e

Es wird eine Rechtsrotation und eine Umfärbung durchgeführt.

(58)

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.

(59)

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 20 30

13 14 17 23 24 27

(60)

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 ≤ in(x):vitreeKeys(ci(x)),

(61)

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 t1 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 2t1 Schlüssel. Damit hat jeder innere Knoten höchstens 2t Kinder. Ein Knoten heißt voll, wenn er genau 2t1 Schlüssel trägt.

Es gilt: t − 1n(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.

(62)

B-Bäume

Beispiel: B-Baum der Ordnung 3

50 Wurzel

10 20 30 40 45 k 55 59 70 75

3 7 8 9 Blatt 13 14 17 Blatt 65 66 Blatt

. . . . . .

Für alle Knoten x mit Ausnahme der Wurzel gilt:

3 − 1 = 2 ≤ n(x) ≤ 2 · 31 = 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.

(63)

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.

(64)

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

n + 1 2

! .

(65)

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.

(66)

Suchen in B-Bäumen

Suchen des Buchstabens Q in B-Bäumen der Ordnung 3:

N

C K S W

A B D E F H L M O Q R T V X Y Z

Q > N

Q < S

Q > O

Q=Q

Suche erfolgreich

N

C K S W

A B D E F H L M O P R T V X Y Z

Q > N

Q < S

(67)

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).

(68)

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

C K S X Y

S

C K W X Y

Einf¨ugen von W

(69)

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).

(70)

Einfügen in B-Bäume

Beispiel: B-Baum der Ordnung 3

C F K Q S

L

K

C F L Q S

H T V

K

C F H L Q S T V

W

K S

C F H L Q T V W

M R N

K S

C F H L M N Q R T V W

P

K N S

(71)

Einfügen in B-Bäume

A B X Y

K N S

A B C F H L M P Q R T V W X Y

D

C K N S

A B D F H L M P Q R T V W X Y

Z

C K N S W

A B D F H L M P Q R T V X Y Z

(72)

Einfügen in B-Bäume

E

ohne One-Pass-Verfahren

C K N S W

A B D E F H L M P Q R T V X Y Z

E

mit One-Pass-Verfahren N

C K S W

A B D E F H L M P Q R T V X Y Z

(73)

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.

(74)

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

F H L M P Q R T V

L¨oschen von M

K P S

F H L N Q R T V

(75)

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

F H L M P Q R T V

L¨oschen von H

N S

F K L M P Q R T V

(76)

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.

(77)

Überblick

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.

(78)

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.

parent(k) = ⌊k2⌋ ist der Index des Vaters des Knotens k.

a.size = n ist die Gesamtlänge des Felds.

(79)

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.

9 6 8 1 5 7 2 0 3 h = 6

n = 9

9 1 6 2

8 3 1 4

5 5

7 6

(80)

Heapsort

Der Algorithmus benötigt 3 Methoden:

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.

(81)

Max-Heapify

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.

(82)

Max-Heapify

17 1

6 2 10 3

11 4 4 5 1 6 7 7

5 8 8 9 2 10 3 11

17 1

11 2 10 3

6 4 4 5 1 6 7 7

5 8 8 9 2 10 3 11

17 1

11 2 10 3

8 4 4 5 1 6 7 7

5 8 6 9 2 10 3 11

(83)

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

(84)

Max-Heapify

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).

(85)

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.

Alle inneren Knoten des Heaps liegen im Bereich ⌊n2⌋. . . 1. Auf diese Knoten muss also Max-Heapify angewendet werden.

(86)

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

(87)

Build-Max-Heap

12 1

6 2 1 3

11 4 3 5 17 6 10 7

5 8 8 9 2 10 4 11

1. Iteration

12 1

6 2 1 3

11 4 4 5 17 6 10 7

5 8 8 9 2 10 3 11

2. Iteration

12 1

6 2 1 3

11 4 4 5 17 6 10 7

5 8 8 9 2 10 3 11

3. Iteration

12 1

6 2 17 3

11 4 4 5 1 6 10 7

5 8 8 9 2 10 3 11

(88)

Build-Max-Heap

12 1

11 2 17 3

6 4 4 5 1 6 10 7

5 8 8 9 2 10 3 11

4. Iteration

12 1

11 2 17 3

8 4 4 5 1 6 10 7

5 8 6 9 2 10 3 11

5. Iteration

17 1

11 2 12 3

8 4 4 5 1 6 10 7

12 6 1 11 3 17 10 5 8 2 4

(89)

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=0

h

2h = 2

ld(h)

X

h=0

n 2h+1

O(h) = O







 n

ld(h)

X

h=0

h 2h









= O







 n

X h=0

h 2h







= O(n)

Build-Max-Heap hat also eine lineare Laufzeit.

(90)

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.

(91)

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 logn).

(92)

Heapsort

12 6 1 11 3 17 10 5 8 2 4

Heap

12 1

6 2 1 3

11 4 3 5 17 6 10 7

5 8 8 9 2 10 4 11

MaxHeapify

17 1

11 2 12 3

8 4 4 5 1 6 10 7

swap

3 1

11 2 12 3

8 4 4 5 1 6 10 7

(93)

Heapsort

MaxHeapify

12 1

11 2 10 3

8 4 4 5 1 6 3 7

5 8 6 9 2 10 17 11

swap

2 1

11 2 10 3

8 4 4 5 1 6 3 7

5 8 6 9 12 10 17 11

MaxHeapify

11 1

8 2 10 3

6 4 4 5 1 6 3 7

5 8 2 9 12 10 17 11

swap, MaxHeapify

10 1

8 2 3 3

6 4 4 5 1 6 2 7

5 8 11 9 12 10 17 11

(94)

Heapsort

MaxHeapify

8 1

6 2 3 3

5 4 4 5 1 6 2 7

10 8 11 9 12 10 17 11

swap,MaxHeapify

6 1

5 2 3 3

2 4 4 5 1 6 8 7

10 8 11 9 12 10 17 11

· · ·

1 1

2 2 3 3

4 4 5 5 6 6 8 7

Array

1 2 3 4 5 6 8 10 11 12 17

(95)

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.

(96)

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

(97)

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

(98)

Prioritätswarteschlangen

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.

Referenzen

ÄHNLICHE DOKUMENTE

ƒ Nachdem auch Person Z ihren Arbeitsauftrag erledigt hat, informiert Person X ihren Auftraggeber über die Erfüllung des Arbeitsauftrags.. ƒ Bei „Erstelle Baum mit 0 Knoten“

 Jeder Knoten in einem Baum ist Wurzel eines Teilbaums des gegebenen Baums..  Beispiele: Der Teilbaum mit Wurzel b ist schwarz markiert und der mit Wurzel d ist

 im minimal belegten Baum gibt es höchstens N(n)-1 Splits, die Wahrscheinlichkeit für einen Split ist daher. C G C C

Ist der Knoten v kein Blatt, so berechnet sich die Balance von v wie folgt: balance = Höhe des linken Teilbaums von v - Höhe des rechten Teilbaums v.. • Erweitern Sie

 Definition AVL-Baum: Ein AVL-Baum b ist ein Binärbaum, für den gilt: Alle Knoten des Baums b erfüllen die AVL-Bedingung?.  Bemerkung: AVL-Bäume wurden 1962 von Adelson-Velski

 Jeder Knoten in einem Baum ist Wurzel eines Teilbaums des gegebenen Baums..  Ein Teilbaum mit Wurzel w besteht aus dem Knoten w, seinen Kindern,

a) Zeichnen Sie einen AVL-Baum der Höhe h=5 mit minimaler Blattzahl N. Höhe) gerade AVL-Bäume mit minimaler Blattzahl untersucht werden.. Höhenbalancierte Bäume 2.

Höhe) gerade AVL-Bäume mit minimaler Blattzahl