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 WurzelA 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