Programmieren - Nichtlineare Datenstrukturen

Werbung
FB Informatik
Prof. Dr. R.Nitsch
Programmieren - Nichtlineare Datenstrukturen
Reiner Nitsch
 [email protected]
Definition und Anwendungen
FB Informatik
Prof. Dr. R.Nitsch
Definition: Eine Prioritätswarteschlange (PQ: PriorityQueue) ist eine Datenstruktur von
Elementen mit Schlüsseln. Sie unterstützt mindestens die folgenden 2 Operationen
Einfügen eines neuen Elements
Entfernen des Elements mit der größten Priorität
Anwendungen
Simulationssysteme: die Zeitpunkte von Ereignissen sind die Schlüssel, die in chronologischer
Reihenfolge zu verarbeiten sind
Auftragsplanung: die Schlüssel (z.B. Eingangsdatum, Auftragssumme, Kunde, …) entsprechen den
Prioritäten und entscheiden über die Reihenfolge der Bearbeitung.
Sortieralgorithmus: Datensätze einfügen und dann nacheinander den jeweils größten entfernen.
Abstrakte Basisklassse:
definiert hier lediglich
dieSchnittstelle
18.01.2013
// Abstrakter Datentyp für elementare Prioritätswarteschlangen
template <typename Item> Verwaltet Elemente vom generischen Typ Item
class PQ {
Vorschläge zur Implementierung?
public:
PQ() {};
Konstruktor erzeugt leere Warteschlange
virtual~PQ() {}
virtual bool empty() const =0;
virtual size_t size() const =0;
virtual void insert( const Item& item ) =0; fügt Kopie von item ein
virtual const Item& getMax() =0; gibt Element mit höchster Priorität zurück
virtual void popMax() =0; entfernt Element mit höchster Priorität
};
Nichtlineare Datenstruktur Baum
2
Elementare Implementierung mit statischem ungeordnetem Array
Template-Implementierung mit ungeordnetem Array
Item: ADT dessen Instanzen durch
eine Priorität gekennzeichnet sind!
FB Informatik
Prof. Dr. R.Nitsch
template <typename Item, size_t MAXN >
class PQ_UnorderedArray : public PQ<Item> { MAXN: Kapazität der Warteschlange!
std::array<Item,MAXN> a;
PQ-SS muss implementiert werden!
size_t N;
public:
PQ_UnorderedArray() { N=0; }
N=0: Std-Ctor erzeugt leere Warteschlange
virtual ~PQ_UnorderedArray() { }
virtual bool empty() const { return (N==0); } Prüfen, ob Warteschlange leer ist
virtual size_t size() const { return N; }
virtual void insert( const Item& item ) { a[N++] = item; } fügt neue Elemente am Ende ein!
virtual const Item& getMax() {
{ if(N==0) throw std::logic_error( "ERROR: Queue is empty!");
Zeitkomplexität? O(1)
size_t idx_max = 0;
for( size_t n=1; n<N; ++n )
Sucht größtes Element in Warteschlange …
if( a[idx_max]<a[n] ) idx_max = n;
… und gibt es zurück.
return a[idx_max];
Zeitkomplexität? O(N)
}
virtual void popMax()
{ if(N==0) return;
size_t max = 0;
for( size_t n=1; n<N; ++n )
Sucht größtes Element in Warteschlange …
if( a[max]<a[n] ) max = n;
std::swap( a[N-1], a[max] ); … vertauscht es mit dem letzten Element …
--N;
… und entfernt es.
}
3
}; 18.01.2013
Vervollständigung und Verbesserung der Schnittstelle
FB Informatik
Prof. Dr. R.Nitsch
Oft ist es unerwünscht, die Datensätze selbst in einer PQ zu sammeln (zu groß,
Resourcenverschwendung durch Kopieren bzw. Bewegen). Es reicht, wenn die PQ z.B. die
Adressen oder Indexe der Datensätze kennt und deren Prioritäten.
Die Operation getMax muss nicht den vollständigen Datensatz mit max. Priorität zurück liefern,
sondern es genügt der Index (oder die Adresse) unter dem dieser Datensatz in einem Array
gespeichert ist.
Eine Anwendung (Client) muss die Prioritäten nicht nur setzen sondern bei Bedarf auch ändern
(Bsp: Dijkstra-Algorithmus) oder wieder entfernen können . Zu diesem Zweck muss die PQ
dem Client Zeiger (Handles) bereitstellen, über die er später angeben kann, auf welche
Elemente sich die Operationen Entfernen oder Ändern der Priorität beziehen sollen.
Achtung: Die Handles dürfen nicht ungültig werden (z.B. durch Bewegen der Elemente im
Speicher), solange die zugehörigen Elemente sich in der PQ befinden.
Anwendung
(Client)
h
key1
data
h
key2
data
PQ
key3
key1
key2
Objekte mit Schlüssel
für Priorität, Handle u.
ggf. assoziierten Daten
18.01.2013
h
key3
data
Prioritätswarteschlangen
5
Verbesserte Schnittstelle
FB Informatik
Prof. Dr. R.Nitsch
// Vollständiger ADT für Prioritätswarteschlangen
template <typename Item>
wie insert; gibt zusätzlich für den Client einen
class PQ
Verweis (Handle) auf die Einfügestelle zurück,
{ private: // Implementierungsabhängiger Code
mit dem er die eingefügten Objekte adressieren
public:
kann, um sie z.B. zu löschen oder ihre Priorität
PQ() {};
zu ändern.
virtual ~PQ() {}
Wichtig: Der Client muss sich darauf verlassen
virtual bool empty() const =0;
können, dass die Handles nicht durch Verschieben
virtual size_t size() const =0;
der Elemente innerhalb der PQ ungültig werden!
virtual Item getMax() =0;
virtual void popMax() =0;
virtual void insert( const Item& item ) =0;
// Implementierungsabhängige Handle-Definition
virtual Handle insert_h( const Item& item ) =0;
virtual void change(Handle handle, const Item& item ) =0;
virtual void remove(Handle handle);
};
Überschreibt den mit handle adressierten
PQ-Eintrag mit dem Wert item. Der Eintrag
Entfernt den durch das Handle
kann so eine geänderte Priorität haben.
bezeichneten Eintrag aus der PQ
18.01.2013
Prioritätswarteschlangen
6
Implementierung mit doppelt verketteter sortierter Liste
FB Informatik
Prof. Dr. R.Nitsch
template <class Item>
class PQ_OrderedList : public PQ<Item>
{ private:
struct Node
{ Item item; Node *pprev, *pnext;
Node( Item item0 ) : item(item0) { pprev = pnext = 0; }
};
typedef Node* Link;
Link pht;
size_t n;
public:
typedef Node* Handle;
// Implementierungsabhängige Handle-Definition
public:
PQ_OrderedList() Erzeugt leere Liste mit 1 Sentinel-Element
{ pht = new Node(Item());
pht->pnext = pht->pprev = pht;
PQ_OrderedList
n = 0;
pht
}
n
virtual bool empty() const { return n==0; }
size_t size() const { return n; }
18.01.2013
Prioritätswarteschlangen
Head-Tail-Element
Node
Element
data
item
---
pnext
pnext
pprev
7
FB Informatik
Prof. Dr. R.Nitsch
virtual Handle insert_h( const Item& item ) // NEU
{ Handle h = new Node( item );
Head-Tail- o.
Head-Tail- o.
Schraffur weist auf
Link p = findInsPos(item);
Nutzelement
Nutzelement
Doppelrolle
als
Nutzh->pnext = p;
1
Node
Node
oder Head-Tail-Elem. hin
2
h->pprev = p->pprev;
O(N)
3
p->pprev->pnext = h;
item 99
item
5
3
4
p->pprev = h;
pnext
pnext
++n;
pprev
pprev
return h;
4
}
Node
private:
item value
virtual Link findInsPos(const Item& item) const
p
1
pnext
{ Link ppos = pht->pnext; 1
2
while( ppos!=pht && ppos->item<item )
pprev
ppos = ppos->pnext; 2
h
ppos
return ppos;
2
1
}
X
X
PQ_OrderedList
Node
Element
pht
data
data egal
data
pnext
pnext
pnext
pprev
18.01.2013
Node
Node
99
data
Node
8
pnext
pprev
Prioritätswarteschlangen
data
5
pnext
pprev
pprev
8
FB Informatik
Prof. Dr. R.Nitsch
virtual void insert( const Item& item )
O(N)
{ insert_h(item); }
virtual const Item& getMax()
{ return pht->pnext->item; }
Node
item
pnext
O(1)
virtual void remove( Handle h ) // NEU
1
{ h->pprev->pnext = h->pnext;
// Ausketten
h->pnext->pprev = h->pprev;
2
delete h; h=0; --n;
O(1)
}
virtual void popMax()
{ remove( pht->pnext ); } O(1)
1
Node
Node
99
item
item value
pnext
pprev
5
pnext
pprev
pprev
h
2
Node
virtual void change(Handle h,const Item& item) //
NEU 99
item
{ h->item = item;
pnext
h->pprev->pnext = h->pnext;
pprev
// Ausketten
h->pnext->pprev = h->pprev;
Link p = findInsertPos(item);
O(N)
h->pnext = p;
1
h->pprev = p->pprev;
2
Einketten vor p
p->pprev->pnext = h; 3
p->pprev = h;
4
}
Node
item
3
X
X
5
pnext
pprev
4
Node
item value
pnext
2
p
1
pprev
h
};
18.01.2013
Prioritätswarteschlangen
9
Quickie zum Thema "Bäume"
FB Informatik
Prof. Dr. R.Nitsch
Wurzelbaum: ist ein gerichteter Graph mit folgenden
Einschränkungen:
er hat genau 1 Knoten mit dem Eingangsgrad 0
(Wurzelknoten)
alle anderen Knoten haben den Eingangsgrad 1, und
er enthält keine Zyklen.
Wurzel
Innerer Knoten
Blatt
Wurzel-Baum
Blatt: Knoten ohne Nachfolger. Alle anderen werden innere Knoten genannt
Tiefe d (Ebene) eines Knotens A: d(A):=Länge des Pfades WurzelA
Tiefe des Baumes: d(T):= maximale Tiefe seiner Knoten
Ebene 0
Wurzel
d=0
Ebene 1
d=1
Ebene 2
d=2
Ebene 3
d=3
Wurzel-Baum
18.01.2013
Anwendungen für Bäume:
o Familienstammbäume,
o Organigramme
o Verzeichnisbäume
o Satzanalyse mit Parse-Baum
o Verarbeitung von Computersprachen
11
Quickie zum Thema binäre Bäume
FB Informatik
Prof. Dr. R.Nitsch
Binärer Baum: ist ein Wurzelbaum, bei dem jeder Knoten
maximal 2 Nachfolger hat.
Vollständiger Binärer Baum: ist ein Binärer Baum, bei dem
Wurzel
Binärer Baum
jede Ebene bis auf die unterste vollständig besetzt ist
auf der untersten Ebene höchstens Blätter ganz rechts fehlen.
ein vollständiger Baum mit N Knoten hat die Tiefe d(T)=int(log2(N))
N=7 Knoten
N=11 Knoten
Wurzel
N=12 Knoten
d=0
d=1
d=2
d=3
Anwendungen für binäre Bäume:
o Prioritätswarteschlangen,
o Heapsort,
o Symboltabellen,
o …
Vollständige Binäre Bäume
18.01.2013
12
Datenstruktur Heap
FB Informatik
Prof. Dr. R.Nitsch
Geordnete Bäume, Schlüsselbäume: die Knoten
dieser Bäume sind Objekte, die Schlüsselwerte
enthalten, für die eine Ordnungsrelation
definiert ist (z.B. < oder ≤)
29
10
39
6
27
92 44
43
2
31
33
11
Binärer Schlüsselbaum
Heap-geordneter binärer Schlüsselbaum: der
Schlüssel in jedem Knoten ist kleiner oder
gleich dem Schlüssel seines Vorgängerknotens
("Papa ist der Beste").
Wurzel enthält den größten Schlüsselwert
Ein Heap ist die Arraydarstellung eines
vollständigen Heap-geordneten binären
Schlüsselbaums (siehe nächste Seite).
18.01.2013
92
27
44
33
29
11
43
31 39
6
2
10
Vollständiger Heap-geordneter
binärer Schlüsselbaum
13
Arraydarstellung eines vollständigen binären Schlüsselbaums
Die Knoten werden Zeile für Zeile von oben
beginnend durchnummeriert und ergeben so die
Indices des Arrays a[1,N] bzw. [0,N-1].
Achtung: das erste Element bekommt den Index 1
bzw. 0.
Vollständige Binärbäume lassen sich ganz einfach in
ein Array abbilden (siehe Abb. rechts).
Es bekommen
29
1
39
 v(k) = k/2 bzw. v(k) = (k-1)/2
und zu jedem Vater-Knoten die beiden Kind-Knoten zu
kennen.
linkes Kind
k = 2*v
bzw.
k = 2*v+1
rechtes Kind
k = 2*v+1 bzw.
k = 2*v+2
18.01.2013
2
6
27
4
8
31
92 44
9
5
10
2
6
33
11
11
12=N
10
3
43
7
a 29 39 10 6 31 2 43 27 92 44 33 11
1 2 3 4 5 6 7 8 9 10 11 12
die Wurzel den Index 1 bzw. 0
Knoten auf der 2. Ebene die Indices 2 (1) und 3(2)
…
Knoten auf Ebene d die Indices 2d-1 bis 2d-1 bzw.
2d-1 –1 bis 2d-2.
für Algorithmen einer Prioritätswarteschlange ist
es wichtig, zu jedem Kind-Knoten k den Vater v
FB Informatik
Prof. Dr. R.Nitsch
Arraydarstellung eines
vollständigen binären Schlüsselbaums
92
1
44
2
39
27
8
4
33
6
31
9
10
5
29
2
11
12
11
6
43
3
10
7
a 92 44 43 39 33 11 10 27 6 31 29 2
1 2 3 4 5 6 7 8 9 10 11 12
Heap
14
Algorithmen für Heaps
FB Informatik
Prof. Dr. R.Nitsch
Alle Algorithmen, die heap-basierte PWS benutzen, können die Priorität bestimmter Elemente im
Heap erhöhen (Fall 1) oder erniedrigen (Fall 2). Danach kann die Heap-Eigenschaft verletzt sein.
Für jeden der beiden Fälle steht dann ein geeigneter Reparatur-Algorithmus zur Verfügung.
Fall 1: Die Priorität eines Elements mit bel. Index k wird erhöht.
 Falls die Priorität des geänderten Elements (Kind k) nun größer, als die seines Vorgängers (Vater v) ist,
ist die Heap-Bedingung auf unterster Ebene verletzt.
 Der Algorithmus fixUp( Item a[], int k ) prüft und
repariert das, falls notwendig.
92
1
So funktioniert fixUp (s. Abb. rechts):
1. Array a enthält den Heap mit der Wurzel bei Index 1.
Der Algorithmus beginnt dort, wo die Priorität (evtl.) erhöht wurde.
Index k kennzeichnet diese Stelle (hier: k=5).
2. Der Index v des Vaters des Kindes k wird berechnet. Wenn die
Priorität des Kindes größer als die des Vaters ist, ist die HeapOrdnung gestört. Durch Tauschen der Werte (hier 44 und 96)
wird der Heap lokal repariert.
3. Durch den neuen Vater kann nun der Heap eine Ebene höher gestört
sein. Der Vater (hier 96) übernimmt deshalb die Rolle des Kindes
und der Vorgang wird mit dem neuen Vater (hier 92) wiederholt,
solange es einen Vater gibt (v>0).
4
96
5
k=5
92
1
k=2
96
39
2
4
v=1
43
3
44
5
3
96
1
92
4
a
Heapbasierte Prioritätswarteschlangen
2
39
39
18.01.2013
43
3
v=2 44
2
44
5
43
3
Heap (wieder ok!)
1 2 3 4 5 6 7 8 9 10 11 12
Bottom-up Methode zur Reparatur eines Heaps
FB Informatik
Prof. Dr. R.Nitsch
92
1
43
3
v=2 44
template < typename Item >
void fixUp( Item a[], int k )
// Bottom-Up Methode zur Reparatur eines Heaps
// Stellt die Heap-Eigenschaft wieder her, falls
// die Priorität des Knotens k vergrößert wurde (s.Abb.).
{ int v = k/2;
while( v>0
// Solange der Vater von k existiert …
&& a[v]<a[k])
// und er nicht mindestens so groß wie sein Sohn ist, …
{
std::swap( a[k],a[v] );
// … müssen sie die Rollen tauschen
k=v; v=k/2; Nun muss noch die Heap-Eigenschaft auf Großvaterebene
}
überprüft und ggf korrigiert werden.
}
Hinweis:
fixUP unterstützt auch das Einfügen neuer Elemente in den Heap.
39
2
4
96
5
92
1
k=2 96
39
2
4
k=5
v=1
43
3
44
5
96
1
43
3
92
39
4
2
44
5
a
1 2 3 4 5 6 7 8 9 10 11 12
 Diese werden dazu hinter dem letzten Element ins Array eingefügt.
 Danach korrigiert man mit fixUp( a, N ) die evtl. gestörte Heap-Ordnung.
 Danach ist wieder sichergestellt, dass die Wurzel die größte Priorität hat (Max-Heap).
Wie muss der Algorithmus angepasst werden, damit er einen Min-Heap korrigiert?
18.01.2013
Heapbasierte Prioritätswarteschlangen
16
Algorithmen für Heaps
FB Informatik
Prof. Dr. R.Nitsch
Fall 2: Die Priorität eines Elements mit bel. Index k wird verringert.
 Falls die Priorität des geänderten Elements nun kleiner, als die seiner Kinder ist, ist die Heap-Ordnung
verletzt.
v=1 29
92
 Der Algorithmus fixDown( Item a[], int v, int N ) prüft
1
und repariert das, falls notwendig.
k=2
43
44
So funktioniert fixDown (s. Abb rechts)
1. Begonnen wird mit dem Element, dessen Priorität erniedrigt wurde
(hier father=92 geändert in 29).
2. Das größte der beiden Kinder wird gesucht (hier: 44)
3. Ist der Heap noch intakt, ist nichts zu tun, weil der Heap in den
unteren Ebenen ja nicht angetastet wurde.
Ist die Heap-Ordnung gestört, werden die Werte von Vater und
größerem Kind getauscht (swap(v,k)).
4. Dadurch verändert sich die Priorität des Kindes (jetzt: 29).
Der Prüf- und Korrekturvorgang muss deshalb bei den nächsten
Nachfahren (=Enkeln; hier: 39 und 33) wiederholt werden. Dazu
übernimmt nun das bisherige Kind die Vaterrolle (v=k) und der
bisherige Enkel (k=2*v) die Kindrolle.
5. Diese Wiederholung wird fortgesetzt, solange es diese Kindrolle
gibt (k<=N).
2
39
27
4
8
11
6
33
6
31
9
10
5
2
k=4 39
8
7
44
1
29
4
10
11
v=2
27
3
11
6
33
6
31
9
10
5
43
3
10
7
44
1
39
2
29
27
8
4
6
31
9
10
33
11
5
6
43
3
10
7
a
18.01.2013
Heapbasierte Prioritätswarteschlangen
1 2 3 4 5 6 7 8 9 10 11 12
Top-down Methode zur Reparatur eines Heaps
FB Informatik
Prof. Dr. R.Nitsch
template < typename Item >
v=1
void fixDown( Item a[], int v, int N )
// Top-Down Methode zur Reparatur eines Heaps,
k=2 44
// falls die Priorität des v-ten von N Elementen
// im Heap verringert wurde.
39 2
33
{ int k = 2*v;
4
5
27
6 31
while (k<=N) Solange ein linkes Kind k des Vaters v existiert …
8
9 10 11
… verletzt dieses möglicherweise die Heap- Eigenschaft.
{
if( j<N && a[k]<a[k+1] ) Gibt es auch den rechten Bruder und ist
v=2
dieser größer, ist er der potentielle Störer.
++k;
29
if(!(a[v] < a[k])) break; Ist der Vater nun nicht mindestens so
k=4 39 2
33
groß, wie sein größeres Kind,
std::swap( a[v], a[k] ); … muss diese Heap-Eigenschaft durch
4
5
27
6 31
v = k; K = 2*v;
Rollentausch wieder hergestellt werden. 8
9 10
}
Nun muss noch die Heap-Eigenschaft auf Enkel}
ebene überprüft und ggf korrigiert werden.
92
29
1
11
6
2
27
8
4
6
31
9
10
10
7
44
1
11
6
43
3
10
7
44
1
39
29
43
3
33
11
5
6
43
3
10
7
a
1 2 3 4 5 6 7 8 9 10 11 12
18.01.2013
Heapbasierte Prioritätswarteschlangen
18
Algorithmen für Heaps
FB Informatik
Prof. Dr. R.Nitsch
Hinweis: fixDown unterstützt auch das Entfernen des Elements mit
der höchsten Priorität. Dieses befindet sich immer an der Wurzel
(Index 1).
Es wird entfernt, indem man es mit dem letzten Element im
Heap überschreibt oder austauscht (1).
Mit fixDown( a, 1, N‐1 ) wird danach die Heap-Ordnung
wieder hergestellt (2 und 3).
Der Heap enthält danach ein Element weniger (N-1).
Aufwandsabschätzung
Ein Baum mit H Ebenen enthält max. N=2H‐1 Elemente:  H=ld(N+1).
fixUp bzw. fixDown müssen maximal H Ebenen (=Iterationen)
durchlaufen
jede Ebene erfordert 1 (fixUp) bzw. 2 (fixDown) Vergleiche.
Ergebnis
Bei einer Heap-basierten Prioritätswarteschlange erfordert
 Einfügen maximal ld(N+1)‐1 Vergleiche und
Entfernen des größten Elements maximal 2ld(N+1)‐1
Vergleiche.
18.01.2013
Heapbasierte Prioritätswarteschlangen
92
29
1
2
43
3
44
2
39
27
4
8
3
6
31
9
10
4
8
5
29
10
11
6
7
N Elemente
11
44
1
43
3
29
2
39
27
1
33
10
11
6
7
N-1 Elemente
33
6
31
9
10
5
?
44
1
39
2
29
27
8
4
11
33
6
31
9
10
5
?
? 6 ?
43
3
10
?
19
7
Heap-basierte Prioritätswarteschlange
FB Informatik
Prof. Dr. R.Nitsch
mit fixUp und fixDown kann der ADT "heap-basierte Prioritätswarteschlange" effizient
implementiert werden:
// maxN: Maximale Größe des Heaps
template < typename Item >
class PQ_Heap : public PQ<Item>
{ typedef Handle int;
Item* pa; // Zeiger auf Priority-Queue in Arraydarstellung
int N;
// Anzahl der Knoten im heap-geordneten Baum
public:
PQ_Heap( size_t maxN ) {
// Index 0 wird nicht genutzt!
pa= new Item[maxN+1];
// Leerer Heap
N= 0;
}
virtual bool empty() const { return N==0; }
Prinzip:
virtual void insert( const Item& item )
1. Neues Element hinten in Heap als Kind einfügen.
{ pa[++N] = item; fixUp(pa,N); }
2. mit fixUp Heap prüfen und ggf. reparieren.
virtual ~PQ_Heap(void) { delete pa; }
Item getMax() { return pa[1]; }
void popMax() {
if(N==0) throw …
std::swap( pa[1], pa[N] ); BackUp des 1. Elements; letztes Element wird neues erstes Element.
fixDown( pa, 1, --N );
Heap-Eigenschaft, beginnend bei erstem Element wieder herstellen
}
Das größte Element mit --N löschen
// Fortsetzung auf nächster Seite
18.01.2013
Heapbasierte Prioritätswarteschlangen
20
Heap-basierte Prioritätswarteschlange
FB Informatik
Prof. Dr. R.Nitsch
virtual void change( Handle h, const Item& item) {
pa[h] = item;
fixUp( pa, h ); fixDown( pa, handle, N );
}
virtual void remove( Handle h) {
swap( pa[h],pa[N]);
fixDown( pa, h, --N );
}
}; //End class PQ_Heap
Problem
Durch die Austauschoperationen (swap) wechseln die Elemente im Heap öfter den
Speicherplatz und damit den Handle.
Die Austauschoperation selbst müsste die Anwendungsdomäne über die jeweiligen neuen
Handle informieren
Lösung für den (häufigen) Fall, dass die Anwendungsdomäne die prioritätsbehafteten
Objekte in einem indizierten Array hält:
Die Anwendungsdomäne verwendet nur ihren Arrayindex (Client-Index) zur Identifizierung
der priorisierten Objekte gegenüber der PQ.
Die PQ kennt zu jedem Client-Index (cidx) den Handle, der das jeweilige Element auf
ihrem Heap (Index-Heap) adressiert.
18.01.2013
Heapbasierte Prioritätswarteschlangen
21
Index-Heap-basierte Prioritätswarteschlange
FB Informatik
Prof. Dr. R.Nitsch
handle
Objekte mit Schlüssel
für Priorität und ggf.
assoziierten Daten
in einem Array [0,N-1]
key1
data
key2
data
key3
data
cidx (client-idx)
0
2
1
3
2
heap
hidx
3
1
0
2
1
3
Index-Heap
Anwendungsdomäne
(Client)
1
Die cidx-Werte sind
die Schlüsselwerte
Prioritätswarteschlange
Die Index-Heap-basierte PQ setzt voraus, dass die Anwendung die priorisierten
Objekte nicht herumschiebt
18.01.2013
Heapbasierte Prioritätswarteschlangen
22
Beispiel: Shortest Paths with Dijkstra algorithm
FB Informatik
Prof. Dr. R.Nitsch
Client: FutureCar-Testbed aus dem Praktikum
Der Graph des Wegenetzes besteht aus 40x33=1320 Knoten, die über Kanten miteinander
verbunden sind.
Jede Kreuzung in FC-City wird von einem Node-Objekt beschrieben (loc, …).
Alle Node sind in einem Array (nodes) in Form einer Hash-Tabelle gesammelt. Eine HashFunktion bildet die Ortskoordinaten auf den Array-Index in idealer Weise ab.
In den Adjazenzlisten der Node-Objekte stehen die Indizes aller direkt erreichbaren
Nachbarkreuzungen (vnext) sowie die Kosten der Verbindung (cost)
Der Dijkstra-Algorithmus verwendet die eigenen Indizes als Handle
x
y

18.01.2013
0,0
1,0
2,0
3,0
0,1
1,1
2,1
3,1
0,2
1,2
2,2
3,2
0,3
1,3
2,3
3,3
nodes
0
1
2
…
42
43
…
1319
Graph
Prioritätswarteschlangen
loc
pred
d2s
42
vnxts
w
2,1
?
?
41 -1
--
loc
pred
d2s
43
vnxts
w
3,1
?
?
42 83
1
30
Node
size_t hash(const Location& loc)
{ return (loc.y*40+loc.x); }
23
Dijkstra Algorithmus (Teil 1)
FB Informatik
Prof. Dr. R.Nitsch
typedef int Index;
void Graph::dijkstra_OneToAll(const Location& s0)
{
PQ_IdxHeap<Index,Comp> queue( nodes.size(),
Comp(this)
);
// Initialisierung
for( Index i=0; i<nodes.size(); ++i){
nodes[i].pred = UINT_MAX;
nodes[i].d2s = UINT_MAX;
}
// define variables
Index vnext, vmin, vs = hash(s0);
Index dneu;
nodes[vs].d2s=0;
for( Index i=0; i<nodes.size(); ++i )
queue.insert(i);
Pseudocode
algorithm dijkstra(G,s)
Eingabe: Graph G, Startknoten s
FOR EACH Knoten v aus V(G) DO
pred[v]=nil
d2s[v]=
OD
d2s[s]=0 // Startknoten höchste Priorität
PQ:=V
// Fortsetzung s. nächste Seite
18.01.2013
Prioritätswarteschlangen
24
Dijkstra Algorithmus (Teil 2)
FB Informatik
Prof. Dr. R.Nitsch
while(!queue.empty() )
{
vmin = queue.getMax(); // Fetch min cost node
if( nodes[vmin].d2s==UINT_MAX) break;
queue.popMax();// ... and remove it from queue
for(Index n=0;n<nodes[vmin].vnxts.size();++n)
{ // Visit and treat all neighbors
vnext = nodes[vmin].vnxts[n];
dneu = nodes[vmin].d2s + nodes[vmin].w[n];
if( dneu < nodes[n].d2s ) {
nodes[n].pred = vmin;
nodes[n].d2s = dneu; // priority change
queue.change(n);
}
}
}
FOR EACH vnext OF vmin DO
dneu:=d2s(vmin)+w(vmin,vnext)
IF dneu<d(vnext) DO
d2s(vnext):=dneu
pred:=vmin
FI
OD
OD
}
18.01.2013
Prioritätswarteschlangen
25
Prioritätswarteschlange für Indexelemente
FB Informatik
Prof. Dr. R.Nitsch
Index-Heap
template < typename Index >
class PQ_IdxHeap : public PQ<Item>
handle
{ typedef int Handle;
hidx
heap
Index *heap; Handle *handle;
2
1
int N;
3
void exch( Index i, Index j ) // neu
2
0
3
{ Index tmp = heap[i]; heap[i]=heap[j]; heap[j]=tmp;
3
1
handle[heap[i]]=i; handle[heap[j]]=j;
}
1
cidx
void fixDown( Index heap[], int v, int N )
{ int k = 2*v;
Prioritätswarteschlange
while (k<=N) {
if( j<N && heap[k]<heap[k+1] )
++k;
if(!( heap[v] < heap[k] )) break;
Predicate für Schlüsselwertvergleich!
std::swap( heap[v], heap[k] );
class Cmp { Dieses Predicate wird vom Client bereitgestellt!
v = k; k = 2*v;
Graph* pg;
}
public:
}
Cmp( Graph* pg0 ) : pg(pg0) {}
void fixUp( Index heap[], int k )
bool operator()(Index a,Index b) const
{ int v = k/2;
{ if(nodes[a].d2s<nodes[b].d2s) return true;
while( v>0 && heap[v]<heap[k]
)
if(nodes[b].d2s<nodes[a].d2s) return false;
{ std::swap(heap[v],heap[k]);
if(nodes[a].loc<nodes[b].loc) return true;
k=v; v=k/2;
if(nodes[b].loc<nodes[a].loc) return false;
}
return false;
}
}
// Fortsetzung auf nächster Seite
};
18.01.2013
Prioritätswarteschlangen
26
Prioritätswarteschlange für Indexelemente
FB Informatik
Prof. Dr. R.Nitsch
public:
PQ_Heap( size_t maxN ) {
{ heap= new Index[maxN+1];
N= 0;
handle
2
3
}
Index getMax() { return heap[1]; }
void popMax() {
if(N==0) throw …
std::swap( pa[1], pa[N] );
fixDown( heap, 1, --N );
}
virtual void change( Index cidx ) {
fixUp(heap,handle[cidx]);
fixDown(heap,handle[cidx],N);
}
1
heap
hidx
3
1
0
2
1
3
Index-Heap
}
virtual ~PQ_Heap(void) { delete heap; }
virtual bool empty() const { return N==0; }
virtual void insert( const Index& cidx)
{ heap[++N] = cidx;
fixUp(heap,N);
cidx
Prioritätswarteschlange
};
18.01.2013
Prioritätswarteschlangen
27
Heap-basierte Prioritätswarteschlange - Eigenschaften
Elementare Implementierungen
mit ungeordnetem Array
mit geordnetem (sortiertem) Array
mit ungeordneter Liste
mit geordneter Liste
Fortgeschrittene Implementierungen
mit Heap-geordnetem Array
insert
getMax
popMax
O(1)
O(N)
O(N)
FB Informatik
Prof. Dr. R.Nitsch
change
remove
O(N)
O(1)
O(1)
O(1)
O(1)
O(N)
O(N)
O(1)
O(N)
O(N)
O(1)
O(1)
O(N)
O(1)
O(1)
O(N)
O(1)
O(lg N)
O(1)
O(lg N)
O(lg N)
O(lg N)
Zeitkomplexitäten bei Prioritätswarteschlangen
18.01.2013
Prioritätswarteschlangen
28
Herunterladen