Algorithmen und Datenstrukturen

Werbung
Was bisher geschah
abstrakter Datentyp : Signatur Σ und Axiome Φ
z.B. ADT Menge zur Verwaltung (Finden, Einfügen,
Entfernen) mehrerer Elemente desselben Typs
Spezifikation einer Schnittstelle
Konkreter Datentyp : Σ-Struktur, die Φ erfüllt
Realisierung
Ein abstrakter Datentyp repräsentiert die Menge aller seiner
möglichen Realisierungen (konkreten Datentypen, die den ADT
erfüllen).
Datenstrukturen zur Verwaltung mehrerer Elemente desselben Typs:
lineare Datenstrukturen:
Arrays (schneller Zugriff auf jedes Element über Index)
Listen (rekursiv)
Stack, Queue (eingeschränkter Zugriff auf spezielle
Elemente)
hierarchische Datenstrukturen:
Bäume (rekursiv)
Spezialfälle: Binärbäume, Unärbäume = Listen
140
Wiederholung ADT Menge
Ziel: Verwaltung (Finden, Einfügen, Entfernen) einer Menge von
Elementen
Sorten: Bool, Element, Menge (hier kurz für MengehElementi)
Signatur:
emptyset :
isEmpty :
Menge →
contains :
Menge × Element →
add, remove : Menge × Element →
Menge
Bool
Bool
Menge
Axiome (alle ∀-quantifiziert: ∀s ∈ Menge∀e ∈ Element):

contains(add(s, e), e) = t,




 contains(remove(s, e), e) = f,
isempty(add(s, e)) = f,
Φ=


add(add(s, e), e0 ) = add(add(s, e0 ), e),



add(add(s, e), e) = add(s, e), . . .











(+ Axiome der Booleschen Algebra)
141
Realisierungen des ADT Menge
in linearen Datenstrukturen (Array):
unsortiert :
I
I
I
contains O(n) (lineare Suche)
add O(1) (anfügen)
remove O(n) (Entfernen und Nachrücken)
sortiert :
I
I
I
contains O(log(n)) (binäre Suche)
add O(n) (sortiertes Einfügen und
Platzschaffen)
remove O(n) (Entfernen und Nachrücken)
(Größe der Eingabe: Anzahl der Elemente)
Aufwand zum sortierten Einfügen lohnt sich bei häufiger Suche
(Ausführung von contains)
142
Wiederholung Binärbaum
data BinTree a = Leaf
| Branch {key::a, left::BinTree a, right::BinTree a}
rekursive Funktionen auf Bäumen, z.B. Durchquerungen
Beispiel: Rekursive Bestimmung aller Teilbäume
Spezifikation:
V: Eingabe Binärbaum t
N: Ausgabe Liste aller Teilbäume von t
rekursives Verfahren:
subtrees :: BinTree a -> [BinTree a]
subtrees t = case t of
Leaf -> [t]
(Branch k l r) -> t : (subtrees l) ++ (subtrees r)
143
Realisierung des ADT Menge durch Binärbäume
Realisierung der Operationen des ADT Menge
emptyset emptyset :: Eq a => BinTree a
emptyset = Leaf
Laufzeit: O(1)
isEmpty isEmpty :: Eq a => BinTree a -> Bool
isEmpty t = case t of
Leaf
-> True
Branch k l r -> False
Laufzeit: O(1)
add (eine von vielen Möglichkeiten)
add :: Eq a =>
add t x = case
Leaf
Branch k l r
BinTree a -> a -> BinTree a
t of
-> Branch x Leaf Leaf
-> Branch k (add l x) r
Laufzeit: O(height(t)) mit
height = Höhe des Baumes (Länge des längsten
Wurzel-Blatt-Pfades)
im ungünstigsten Fall (Baum ist Pfad): O(n)
144
Binärbäume: contains
Spezifikation der Funktion contains (auf ADT Menge):
V: Eingabe (M, x) mit Menge M ⊆ A und x ∈ A
N: true, falls x ∈ M
false, sonst
Spezifikation der Funktion contains (auf Binärbaum):
V: Eingabe (t, x) mit Binärbaum t mit Schlüsseln aus A
(repräsentiert die Menge) und x ∈ A
N: true, falls t einen Teilbaum Branch(k , l, r ) mit x = k enthält
false, sonst
in Haskell:
contains :: Eq a =>
contains t x = case
Leaf
->
(Branch k l r) ->
BinTree a -> a -> Bool
t of
False
(k == x)
|| contains l x
|| contains r x
Laufzeit: O(n)
145
Binäre Suchbäume
Der Binärbaum t hat die Suchbaum-Eigenschaft gdw.
für jeden Teilbaum t 0 = Branch(x, l, r ) von t gilt:
I
für jeden Schlüsselwert y eines inneren Knotens von l gilt
y <x
I
für jeden Schlüsselwert v 0 eines inneren Knotens von r gilt
x <y
Ergebnis der Inorder-Durchquerung jedes binären
Suchbaumes ist eine aufsteigend sortierte Folge
Laufzeit für sortierte Ausgabe aller im Suchbaum mit n Knoten
enthaltenen Schlüssel: O(n)
146
Binäre Suchbäume: contains
Spezifikation der Funktion contains (auf binärem Suchbaum):
V: Eingabe (t, x) mit binärem Suchbaum t mit Schlüsseln aus A
(repräsentiert die Menge) und x ∈ A
N: true, falls t einen Teilbaum Branch(k , l, r ) mit x = k enthält
false, sonst
Idee (rekursiv):
IA: Der leere Baum Leaf enthält keinen Schlüssel.
IS: Baum Branch(k , l, r ) enthält x falls
I x = k oder
I x < k und linkes Kind enthält x oder
I x > k und rechtes Kind enthält x
contains
contains
Leaf
Branch
if x
:: Ord a => BinTree a -> a -> Bool
t x = case t of
-> False
k l r -> if x == k then True else
< k then contains l x
else contains r x
147
Laufzeit der Suche
Laufzeit von contains t x hängt von
Größe der Menge (Anzahl n der Knoten in t) ab:
I
falls t einen Teilbaum Branch(x, l, r ) enthält:
Länge des Pfades von Wurzel zu Teilbaum
I
falls t keinen Teilbaum Branch(x, l, r ) enthält:
Länge des Wurzel-Blatt-Pfades (Höhe des Baumes t)
höchstens Höhe des Baumes t (log(n) ≤ height(t) ≤ n)
Suche im Suchbaum t in O(height(t))
(i.A. schneller als lineare Suche in einer Folge)
148
Extremwerte in binären Suchbäumen
Spezifikation Minimum:
V: Eingabe binärer Suchbaum t (Wurzel)
N: Ausgabe m: minimaler in t vorkommender Schlüsselwert
(undefiniert für leeren Baum)
Idee: in jedem binären Suchbaum steht der minimale Schlüsselwert
im äußeren linken Knoten
(Minimum lässt sich ohne Schlüsselvergleich bestimmen.)
mini :: Ord a
mini t = case
Leaf
Branch k
Branch k
=> BinTree a -> a
t of
-> undefined
Leaf r -> k
l
r -> mini l
Laufzeit höchstens Tiefe des Baumes t (log(n) ≤ tiefe(t) ≤ n)
Maximum analog (äußerer rechter Knoten)
149
Einfügen in binäre Suchbäume
Spezifikation add:
V: Eingabe (t, x) mit binärem Suchbaum t mit der Schlüsselmenge
{x1 , . . . , xn } und Schlüsselwert x
N: Ausgabe binärer Suchbaum t 0 mit der Schlüsselmenge
{x1 , . . . , xn , x}
Idee (rekursiv):
IA: Einfügen in leeren Baum durch Erzeugung eines Knotens
IS: in jedem Teilbaum:
I schon vorhandene Schlüssel nicht einfügen (Menge)
I Schlüssel < Wurzelschlüssel in linkes Kind einfügen
I Schlüssel > Wurzelschlüssel in rechtes Kind einfügen
add :: Ord a => BinTree a -> a -> BinTree a
add t x = case t of
Leaf
-> Branch x Leaf Leaf
Branch k l r -> if x == k then t
else if x < k then Branch k (add l x) r
else Branch k l (add r x)
150
Iteriertes Einfügen
Beim Einfügen der Elemente der Menge {2, 3, 5, 7} in
verschiedenen Reihenfolgen in Leaf entstehen i.A.
veschiedene Bäume
Beispiel (Tafel):
I
3,7,5,2
I
5,3,7,2
I
2,3,5,7
Daraus lässt sich ein Sortierverfahren ableiten.
Sortieren durch Einfügen in binären Suchbaum:
1. schrittweises Einfügen aller Elemente einer Menge in
einen zu Beginn leeren binären Suchbaum
2. Sortierte Ausgabe durch Inorder-Durchquerung des so
entstandenen binären Suchbaumes
151
Laufzeit Einfügen
Idee: Schlüsselwert x wird dort in t eingefügt, wo ihn der
Suchalgorithmus (contains) in t finden würde:
I
falls x schon vorhanden: kein Einfügen
I
falls x noch nicht vorhanden (Blatt):
Ersetzen des Blattes durch neuen Knoten
Branch x Leaf Leaf
also dieselbe Laufzeit wie contains:
höchstens Höhe des Baumes t (log(n) ≤ height(t) ≤ n)
Einfügen in einen binären Suchbaum mit n Knoten in
O(height(t))
Laufzeit des abgeleiteten Sortierverfahrens: O(n height(t)),
152
Löschen aus binären Suchbäumen
Spezifikation remove:
V: Eingabe (t, x) mit binärem Suchbaum t mit Schlüsseln
{x1 , . . . , xn } und Schlüsselwert x
N: Ausgabe binärer Suchbaum t 0 mit Schlüsseln {x1 , . . . , xn } \ {x}
mögliche Fälle:
1. x kommt nicht in t vor: Ergebnis t 0 = t
2. x ist Schlüsselwert eines Teilbaumes s in t mit zwei leeren
Kindern:
Löschen des Knotens s (Ersetzen durch Leaf)
3. x ist Schlüsselwert eines Knotens s in t mit einem leeren Kind:
Ersetzen des Knotens s durch sein einziges nichtleeres Kind
4. x ist Schlüsselwert eines Knotens s in t mit zwei nichtleeren
Kindern:
Tausch der Schlüsselwerte in s und dem linken äußeren Knoten
r des rechten Kindes von s (Minimum des rechten Kindes)
Löschen des Knotens r (rekursiv)
Warum hat der so entstandene Baum die Suchbaumeigenschaft?
Laufzeit für Löschen aus binärem Suchbaum t: O(height(t))
153
Binäre Suchbäume: remove
remove :: Ord a => BinTree a -> a -> BinTree a
remove t x = case t of
Leaf
-> Leaf
Branch k Leaf Leaf ->
if x == k then Leaf else t
Branch k l Leaf
->
if x == k then l else Branch k (remove l x) Leaf
Branch k Leaf r
->
if x == k then r else Branch k Leaf (remove r x)
Branch k l r
->
if x == k then Branch (mini r) l (remove r (mini r))
else if x < k
then Branch k (remove l x) r
else Branch k l (remove r x)
154
Laufzeiten in binären Suchbäumen
I
contains
I
add
I
remove
in O(height(t))
Laufzeiten in Abhängigkeit von der Knotenzahl n = size(t):
Extremfälle:
I
für Pfade (entartete Bäume) height(t) = size(t)
Laufzeit der Operationen O(n)
I
für balancierte Bäume height(t) = log size(t)
Laufzeit der Operationen O(log(n))
155
Anwendung von Suchbäumen
(Balancierte) binäre Suchbäume sind geeignet zum Speichern und
schnellen Wiederfinden von Daten anhand ihnen zugeordneten
Schlüsselwerten
Binäre Suchbäume:
I
Realisierung des ADT Menge für linear geordnete Mengen
I
Realisierung von Wörterbüchern
(= Mengen von Paaren (Schlüssel, Wert) mit eindeutigem
Schlüssel aus einer linear geordneten Menge, z.B. ⊆ )),
(= partielle Funktionen: Schlüsselbereich → Wertebereich):
Operationen: contains, add, remove, jeweils von Paaren
(Schlüssel, Daten)
z.B. Telefonbucheinträge über Namen (alphabetisch geordnet),
Studenten über Studentennummern
N
156
Balancierte binäre Suchbäume
Laufzeit für Suche, Einfügen, Löschen in Baum t mit n Knoten:
O(height(t))
Ziel: Laufzeit für Suche, Einfügen, Löschen O(log(n))
Idee: balancierte Suchbäume, in denen die Tiefe jedes
Blattes (etwa) gleich ist (also O(log(n)))
157
Vollständig balancierte Bäume
Binärer Suchbaum t heißt vollständig balanciert, wenn in jedem
Knoten Branch(x, l, r ) in t gilt:
|size(l) − size(r )| ≤ 1
Tiefe jedes Knotens ≤ blog(n)c + 1
Beispiel (Tafel)
158
Operationen in vollständig balancierten Bäumen
emptyset O(1)
isEmpty O(1)
contains O(log(n))
add, remove in je zwei Schritten:
1. Einfüge- und Löschoperation für balancierte
binäre Suchbäume: O(log(n))
2. Rebalancieren, d.h. Wiederherstellen der
vollständigen Balance: i.A. aufwendig
159
AVL-Bäume
(Adelson-Velskii, Landis, 1962)
Binärer Suchbaum t heißt AVL-Baum, wenn in jedem Teilbaum
Branch(k , l, r ) in t gilt:
|height(r ) − height(l)| ≤ 1
(AVL-Eigenschaft)
Optimierung:
Speichern der Balance height(r ) − height(l) in jedem Knoten
erlaubt schnellen Test
Laufzeiten:
contains O(log(n))
add, remove Summe der Laufzeiten für
1. add, remove in balancierten binären
Suchbäumen: O(log(n))
2. Wiederherstellung der AVL-Eigenschaft: ?
160
Rebalancieren in AVL-Bäumen
notwendig bei Verletzung der AVL-Eigenschaft im Teilbaum
Branch(k , l, r ) mit |height(l) − height(r )| = 2
geschieht nach Einfügen (als neues Blatt) oder
Löschen (rekursives Ersetzen durch einziges Kind oder Minimum im
rechten Teilbaum)
evtl. Verletzung in mehreren Vorgängern des eingefügten Knotens
Fälle vor dem Einfügen: Balance =
0: height(l) = height(r )
nach Einfügen keine Verletzung der AVL-Eigenschaft
−1: height(l) = height(r ) + 1
I
I
nach Einfügen in r keine Verletzung der AVL-Eigenschaft
nach Einfügen in l evtl. Verletzung der AVL-Eigenschaft
+1: height(l) + 1 = height(r )
I
I
nach Einfügen in l keine Verletzung der AVL-Eigenschaft
nach Einfügen in r evtl. Verletzung der AVL-Eigenschaft
(Fälle −1 und +1 sind symmetrisch)
161
(Einfache) Rotation in AVL-Bäumen
bei Verletzungen der AVL-Eigenschaft im tiefsten Teilbaum k der
Form
k = Branch(x, Branch(y , l, r ), s)
mit height(l) − 1 = height(r ) = height(s)
(Balance im Teilbaum mit Wurzelschlüssel x: −2, y: −1)
Ersetzung von k durch
lrotate(k ) = Branch(y , l, Branch(x, r , s))
(Linksrotation)
analog (symmetrischer Fall):
Verletzungen der AVL-Eigenschaft im tiefsten Teilbaum k der Form
k = Branch(x, l, Branch(y , r , s))
mit height(l) = height(r ) = height(s) − 1
(Balance im Teilbaum mit Wurzelschlüssel x: +2, y: +1)
Ersetzung von k durch
rrotate(k ) = Branch(y , Branch(x, l, r ), s)
(Rechtsrotation)
162
Doppel-Rotation in AVL-Bäumen
bei Verletzungen der AVL-Eigenschaft im tiefsten Teilbaum der Form
k = Branch(x, Branch(y , l, Branch(z, r , s)), t)
mit Balance im Teilbaum mit Wurzelschlüssel x: −2, y: +1
Ersetzung von k durch
lrrotate(k ) = Branch(z, Branch(y , l, r ), Branch(x, s, t))
Links-Rechts-Rotation besteht aus zwei aufeinanderfolgenden
einfachen Rotationen: rrotate(Left(k )) danach lrotate(k )
symmetrischer Fall:
k = Branch(x, t, Branch(y , Branch(z, l, r ), s))
mit Balance im Teilbaum mit Wurzelschlüssel x: +2, y: −1
Ersetzung von k durch
rlrotate(k ) = Branch(z, Branch(x, t, l), Branch(y , r , s))
Rechts-Links-Rotation besteht aus zwei aufeinanderfolgenden
einfachen Rotationen: lrotate(Right(k )) danach rrotate(k )
163
Laufzeit der Operationen in AVL-Bäumen
Rebalancieren (Rotationen): O(1)
Höhe von AVL-Bäumen mit n Knoten: O(log(n))
contains O(log(n))
add O(log(n))
remove O(log(n))
164
Herunterladen