i+1..n

Werbung
Kapitel 5: Von Datenstrukturen zu Abstrakten Datentypen
5.1 Zur Erinnerung: Datenstrukturen in Pascal
Listenverarbeitung in Pascal
5.2 Abstrakte Datentypen und Objektorientierung
Brüche als ADT Implementierung - objektorientiert
Brüche als ADT Implementierung - funktional
5.3 Geordnete lineare Zusammenfassungen: Listen bzw. Folgen
Cons-Zelle als Basisklasse
Statisch-funktionale Listenimplementierung
Listenimplementierung mit Objektmethoden
5.4 Stacks und Queues
Zustandsorientierte Stack-Implementierung
5.5 Priority Queues
5.6 Find and Merge
5.6.1 Implementierung mit Arrays
5.6.2 Implementierung mit Bäumen
1
Implementierung von Queues:
• Mit doppelt verketteter Liste als Zeigerstruktur
Doppelt verkettet, da man auf beide Enden der
Liste zugreifen muss.
• In einem Array
Problem: Einfügen am Ende und Entfernen am
Anfang führt dazu, dass die Queue „durch den
Array wandert“.
Lösung: Zyklisch in einem Array
2
Anwendungen von Queues
• Realisierung von Warteschlangen,
besonders in Betriebssystemen, z.B.
Prozesse, Nachrichten, Druckaufträge.
3
5.5
Priority Queues:
Mengen mit Insert und Deletemin
Priority Queues: Warteschlangen, in denen die Elemente
Prioritäten haben.
Es können Elemente mit beliebiger Priorität eingefügt
werden und
es wird immer das (ein) Element mit der höchsten Priorität
entnommen.
Anwendungen/Beispiele:
• Patienten im Warteraum einer Krankenhausambulanz
• Betriebssysteme, bes. Multiuser-Betriebssysteme
4
Oft können verschiedene Elemente gleiche Priorität haben:
führt zu Multisets (Multimengen)
• Multisets: Mengen mit Duplikaten (aber ohne Ordnung
der Elemente, also keine Listen/Folgen).
Beispiel: {|1,3,3,7,7,7,10|}
auch notiert als: {(1,1),(3,2),(7,3),(10,1)}
• Zu einer Menge S sei M(S) die Menge aller Multimengen
mit Elementen aus S.
In Priority Queues:
• Prioritäten = natürliche Zahlen
• Höhere Priorität = kleinere Zahl
(dann gibt es automatisch eine höchste Priorität).
5
Datentyp für die Priority Queue,
algebraische Spezifikation:
algebra pqueue
sorts
pqueue, elem
ops
empty:
 pqueue
isempty:
pqueue
 boolean
insert:
pqueue  elem  pqueue
deletemin: pqueue
 pqueue  elem
sets
pqueue = M(elem)
functions
empty = {}
isempty (p) = (p={})
insert (p,e) = p  {|e|}
deletemin (p) = (p',e)
mit e = min(p) und p'=p\{|e|} falls p{},
undefiniert
sonst
end pqueue.
6
Implementierung von Priority Queues
Z.B. mit partiell geordneten Bäumen.
• Ein partiell geordneter Baum (Heap) ist ein
knotenmarkierter binärer Baum (T, m) (hier ist T der
Baumgraph und m die Markierungsfunktion der Knoten),
in dem für jeden Teilbaum (T ',m) mit der Wurzel x
gilt:
für alle y aus T ' ist m(x)  m(y)
In der Wurzel steht also jeweils das Minimum eines
Teilbaumes.
Analog möglich: Ordnung umgekehrt, also in jeder
Wurzel eines Teilbaums das Maximum.
7
Definition Heap
• Speziellere Definition eines Heap:
partiell geordneter Binärbaum, der linksvollständig ist, d.h. die Ebenen werden von der
Wurzel her gefüllt, und jede Ebene von links
nach rechts.
Außerdem: Implementierung in einem Array, in
dem die Knoten in dieser Reihenfolge abgelegt
werden.
8
Teilheap
Ein Teil T [ i..k ] ( 0  i  k  n ) heißt Teilheap, wenn gilt:
für alle j aus {i,...,k} ist
m(T [ j ])  m(T [ 2j ]) falls 2j  k
und
m(T [ j ])  m(T [ 2j+1]) falls 2j+1  k
Ist T [i+1..n] bereits ein Teilheap, so kann man durch
Einsinkenlassen des Elements T [ i ] die Heapeigenschaft
erweitern.
9
Aufbau des Heaps
Zum Aufbau des Heaps werden die Elemente zunächst
in die Ebenen unterhalb der Wurzel eingebracht und zwar
maximal 2 j Elemente in die Ebene j (j = 0,..., k-1).
Jedes Einsinkenlassen eines Elementes kostet den
Aufwand O(k-1) = O(ld n), also bei n Elementen O(n ld n).
Eine genauere Betrachtung zeigt, dass der Aufwand
pro Element in jeder j-Ebene nur O(k-j-1) ist.
Also erhält man allgemein
1 •2 k-2 + 2 • 2 k-3 + ... + (k-1) • 2 0  2 k-1 i > 0 i / 2i = 2 k = O(n)
Hat man nun das oberste Element entfernt, so wird das
letzte Element an seine Stelle gebracht und durch Einsinkenlassen (pro Ebene zwei Vergleiche und eine Vertauschung)
die Heapeigenschaft wiederhergestellt.
10
Dies kostet einen Aufwand von O(ld n).
Entfernen des minimalen Elementes aus einem
Heap und Wiederherstellung der Heapeigenschaft
Algorithmus deletemin (h)
{lösche das minimale Element aus dem Heap h
und gib es aus}
Entnimm der Wurzel ihren Eintrag und gib ihn
als Minimum aus. Nimm den Eintrag der
letzten besetzten Position im Baum, lösche
diese Position und setze ihren Eintrag in
die Wurzel ein.
Sei p die Wurzel, und seien q und r die
Söhne der Wurzel
Solange Söhne q oder r existieren und
(m(p) > m(q) oder m(p) > m(r) ) gilt:
{vertausche p mit dem Sohn mit dem
kleineren Eintrag. Setze q und r auf die
11
neuen Söhne dieses Knotens.}
Einfügen in einen Heap:
Algorithmus insert (h, e)
{füge einen neuen Knoten q mit Eintrag e in
den Heap h ein}
erzeuge einen neuen Knoten q mit Eintrag e;
füge q auf der ersten freien Position in
der untersten Ebene ein; falls die
unterste Ebene voll besetzt ist, beginne
eine neue Ebene.
Sei p der Vater von e;
Solange p existiert und m(p) > e gilt:
{Vertausche die Einträge in p und q, setze
q auf p und p auf den Vater von p.}
12
Sei n die Zahl der Knoten des Heap.
Aufwand
• für das Entfernen des minimalen Elementes: O(log n).
• für das Einfügen eines neuen Knotens: O(log n).
Denn ein Heap hat Höhe [log2 (n+1)]+ -1
Aufwand
• für das Aufbauen des Heap durch wiederholtes Einfügen
von Knoten: O(n log n).
Wir haben gesehen, dass man einen Heap in O(n)
Schritten aufbauen kann, wenn diese Elemente von
Anfang an alle bekannt sind.
13
5.6 Partitionen von Mengen mit Find und
Merge
Sei S eine Menge,
P eine Partition von S, also eine Menge von Teilmengen von
S, die paarweise disjunkt sind und zusammen die Menge
S ergeben.
(Zwei Elemente von S heißen äquivalent, wenn sie zur
gleichen Komponente gehören.)
Wir wollen:
• find: bei Eingabe eines Elementes die Komponente
finden, zu der es gehört,
• merge: bei Eingabe (der Namen oder von Elementen)
zweier Komponenten diese verschmelzen.
14
Datentyp, algebraisch spezifiziert:
algebra partition
sorts partition, compname, elem
ops
empty :  partition
addcomp: partition  compname  elem  partition
merge : partition  compname  compname  partition
find : partition  elem  compname
sets
compname=Menge von Komponentennamen
(aus einer Menge CN)
partition={(ci ,Si) | 1  i  n, ci Komponentenname,
Si Teilmenge von elem,
ij  ci  cj und Si, Sj elementfremd.}
15
functions
empty bildet auf die leere Partition ab (dann ist auch die
partitionierte Menge S die leere Menge!)
Sei p:={(c1,S1), ..., (cn,Sn)} eine Partition,
S = Vereinigung der Si, und C= {c1, ..., cn}, C Komp. N.
Sei a aus CN\C, x nicht in S enthalten.
addcomp(p,a,x) =p U {(a,{x})}
Seien a und b Elemente aus C mit den zugehörigen
Komponenten A und B, d ein Name aus CN\C oder
d=a bzw. d=b.
merge (p,a,b) = (p \ {(a,A), (b,B)}) U {(d,A U B)}
Sei x ein Element aus S.
find (p,x) = a mit (a,A) aus p und x aus A.
end partition.
16
5.6.1
Implementierung mit Arrays
Fall elem := {1,..., n} und compname = {1,..., n}.
Wir benutzen zwei Arrays der Größe n.
1. Adjazenzmatrix (components):
enthält die Komponentennamen und zu jedem ein
Element der Komponente (wenn dies 0, dann
Komponente leer).
1. Inzidenzmatrix (elems):
enthält die Elementnamen, zu jedem den Namen der
zugehörigen Komponente und ein weiteres Element der
Komponente (wenn dies 0, dann kein noch nicht
erfasstes Element der Komponente).
Man erhält für jede Komponente eine verkettete Liste!
17
p = {(2,{1,2,4,5}), (3,{3}), (6,{6})}. S = {1,2,3,4,5,6}.
Beispiel
components
compname
Vertreter (0
unbenutzt)
1
0
2
1
mit 1
3
3
mit 3
4
0
5
0
6
6
mit 6
elems
compname
nextelem
1
2
2
2
2
4
3
3
0 (entspricht NIL)
4
2
5
5
2
0
6
6
0
verlinkt mit
18
Algorithmus find: Zeit O(1).
Algorithmus merge (p, a, b);
{verschmelze die Komponenten von a und b in der
Partition p}
Durchlaufe für a die zugehörige Liste in der
Inzidenzmatrix elems und setze für jedes Element der
Liste den zugehörigen Komponentennamen compname
auf b;
Sei j der Name des letzten Elements dieser Liste;
Führe dann die folgenden Umbenennungen durch.
elems[j].nextelem:= components[b].firstelem;
components[b].firstelem:=components[a].firstelem;
components[a].firstelem:=0;
end merge.
19
Aufwand für merge: O(n).
Aufwand für n-1 merge-Anweisungen:
Klar: O( Σi=1…n i) = O(n²).
Kann verbessert werden zu: O(n log n):
Schlage immer die kleinere Komponente zur
größeren!
Dann immer eine (mindestens) Verdoppelung
der Größe der Komponente eines Elements.
Amortisierte worst-case Laufzeit von merge:
O(log n).
20
5.6.2 Implementierung mit Bäumen
Wir benutzen:
• ein Array mit den Elementen und
• für jedes Element einen Knoten in einem
Baum,
• sowie zu jeder Komponente eine
Baumstruktur.
Name einer Komponente: das Element in
der Wurzel des Baums.
21
Implementation mit Bäumen
elems
1
-> 1
2
-> 2
3
-> 3 -> K3 -> 3
4
-> 4
-> 2
-> 1 -> K2 -> 1
5
-> 5
-> 2
-> 1
6
-> 6 -> K6 -> 6
-> 1
Hier wird jede Komponente durch einen Baum dargestellt, dessen Knoten die
Elemente der Komponente sind. Dann ist elems ein array[1..n] von diesen
Bäumen. Die Verweise führen von den Söhnen zum Vater, die Namen der
Komponenten sind Zeiger auf die Wurzeln der zugehörigen Bäume: Der
Wurzelknoten enthält neben dem Namen der Komponente noch deren Größe.
22
Algorithmen
• find(p,x): starte im Array bei x und laufe zur
Wurzel des Baums der Komponente.
Aufwand: O( Höhe des Baums).
• merge(p,a,b): Mache a zum Sohn von b oder
umgekehrt.
Aufwand: O(1).
Höhenbeschränkung zu erreichen durch: Wurzel
der kleineren Komponente wird Sohn der Wurzel
der größeren Komponente.
Dann: Höhe  log n.
23
Pfadkompression
Noch eine Verbesserung: Pfadkompression:
bei find(p,x) mache alle Knoten auf dem Pfad
von x zur Wurzel zu Söhnen der Wurzel.
Dann n find-Operationen beinahe in linearer Zeit:
O(n • G(n))
Dabei wächst G sehr langsam:
G(n)  5 für n  265536.
24
Kapitel 6: Suchbäume und weitere
Sortierverfahren
6.1 Binäre Bäume
Die Klasse BinTree mit Traversierungsmethoden
6.2 Suchbäume
6.2.1 AVL Bäume
6.3 HeapSort und BucketSort
6.3.1 HeapSort
6.3.2 BucketSort
25
6.1
Binäre Bäume
Bäume als Graphen:
• Gerichtete Graphen: bestehen aus Knoten (Knotenmenge V)
und Kanten (Kantenmenge E:Teilmenge von V V).
• Bäume: spezielle gerichtete Graphen:
(1) haben einen ausgezeichneten Knoten, die Wurzel w,
(2) haben die Eigenschaft, dass zu jedem Knoten k des
Baumes genau eine Kantenfolge (ein Pfad) existiert, die w mit
k verbindet.
(3) haben keine Zykeln, d.h. es gibt keine Kantenfolge, die
den gleichen Knoten mit sich verbindet.
• Bäume: andere Charakterisierung: zusammenhängende
gerichtete Graphen mit #(V) = #(E) + 1.
26
Graphische Darstellung
• Bäume werden graphisch i.d.R. so dargestellt,
dass die Wurzel die oberste Position einnimmt
und Kanten von oben nach unten gerichtet sind
(Hierarchie).
• Knoten, von denen keine weiteren Kanten
ausgehen, heißen äußere Knoten oder Blätter.
• Die anderen Knoten heißen innere Knoten.
27
Beispiel: Binärbaum zum Ausdruck ((12/4)•2)
Hier:
• innere Knoten: Kreise
• Blätter: Rechtecke
28
Bezeichnungen
• Ist (k,k´) eine Kante in einem Baum, so heißt
k´ Kind (Sohn, Nachfolger) von k und
k Vorgänger von k´.
• Nachfolgerrelation eines Baumes:
{ (k, k´) | k innerer Knoten, k´ Nachfolger von k´}
• Vorgängerfunktion eines Baumes: partielle
Funktion, die jedem Knoten außer der Wurzel
den Vorgänger zuordnet.
29
Binärbäume: Bäume, in denen jeder Knoten
maximal zwei Nachfolger hat.
Direkte Definition von Binärbäumen:
T ist genau dann ein Binärbaum (mit
Knotenelementen aus M), wenn
(i) T = < T1, x, T2 > wobei T1, T2 Binärbäume
und x aus M oder
(ii) T = < > (leere Struktur, null)
30
Hier:
• Bei einem Binärbaum T = < T1, x, T2 > ist x die Wurzel
des Baums.
• Knoten: die Wurzeln von Teilbäumen.
• Kanten: von der Wurzel x eines Teilbaumes
T = < T1, x, T2 > zu den Wurzeln von T1 und T2.
• Wir identifizieren im Folgenden häufig einen Knoten mit
dem entsprechenden Wert (Inhalt) x.
• Ein Knoteninhalt kann in einem Binärbaum mehrfach
vorkommen.
• Eindeutige Identifizierung eines Knoten: durch Links/Rechts-Entscheidungen von der Wurzel zu dem Knoten:
Bitfolge.
• Diese Definition kann auf k-näre Bäume erweitert werden.
31
Pfade
• Pfad: Knotenfolge x0 , x1 , ... , xn , wobei jeweils
xi+1 Nachfolger (Kind) von xi ist, d.h. (xi,xi+1)
Kante.
• Länge eines Pfades = Zahl der Kanten, hier also
n.
Ein nur aus einem Knoten bestehender Pfad hat
die Länge 0.
32
• Die Höhe eines Baumes T ist die maximale
Länge eines Pfades in T.
(Die Anzahl der Ebenen im Baum ist um eins
größer als die Höhe des Baumes.)
• Ein Binärbaum heißt vollständig, wenn alle
Ebenen bis auf die letzte vollständig besetzt
sind.
• … links-vollständig, wenn er vollständig ist und
die unterste Ebene von links nach rechts
aufgefüllt ist.
• Auch diese Definitionen gelten für k-näre Bäume
33
Herunterladen