// sl-4-BA-Inf2: Datenstrukturen für Graphen (Adjazenzmatrix, Adjazenzliste) // Graphalgorithmen: Tiefensuche, Breitensuche, kürzeste Wege (Dijkstra) // Begriffe: // gerichteter Graph G=(V,E) mit Knotenmenge V und Kantenmenge E aus VxV // (ungerichteter Graph G=(V,E), Richtung der Kanten weglassen) // Kante (x,y) ist inzident zu ihren Knoten x und y; x und y sind adjazent/benachbart // S(x) Menge der Nachfolger = { y | (x,y) in E } // P(x) Menge der Vorgänger = { y | (y,x) in E } // N(x) Menge der Nachbarn = { y | (x,y) in E oder (y,x) in E } // d+(x) = |S(x)| Ausgangsgrad, d-(x) = |P(x)| Eingangsgrad, d(x) = d+(x) + d-(x) Grad // (x,x) heißt Schlinge // Adjazenzmatrix A=(a(i,j)) zum Graphen G=(V,E) mit V={v1,...,vn}, // a(i,j)=1 gdw. (vi,vj) ist Kante in E // gewichtete Graphen G=(V,E,c), c:E->R // in diesem Programm Adjazenzmatrix eines gewichteten Graphen // a(i,j)=x, x!=0 gdw. (vi,vj) ist Kante in E mit Gewicht c(vi,vj)=x // Adjazenzliste: Liste von Knoten, jeder Knoten speichert Liste seiner Nachfolger // In der Regel liegen Graphen als Adjazenzlisten vor, in diesem Programm wird // Darstellung als Matrix hauptsächlich zur einfachen Generierung der // Zufallsgraphen verwendet sowie als weitere Datenstruktur zur möglichen // Darstellung von Graphen im Rechner // Übungsaufg.: Wandeln Sie (von Hand) Adjazenzmatrizen in Adjazenzlisten um, // "malen" Sie die Graphen und führen Sie eine Tiefen- und Breitensuche durch. // Bestimmen Sie (von Hand) mit Dijkstras Algorithmus die kürzesten Wege // zwischen allen Paaren von Knoten (wie oft muss dazu der Dijkstra-Algorithmus // ausgeführt werden?) #include "time.h" #include using namespace std; const int ANZ_KNOTEN = 7; int ZufaZa() { return rand()%9 + 1; // erzeugt eine 1-stellige Zufallszahl != 0 // return rand()%90 + 10; // erzeugt eine 2-stellige Zufallszahl } int AdjMat[ANZ_KNOTEN][ANZ_KNOTEN]; void InitAdjMat(int mat[ANZ_KNOTEN][ANZ_KNOTEN]) { // sehr(!!!) einfacher Zufallsgraphgenerator for(int i=0; i=0;j--) { // Kanten werden jeweils vorne eingefügt, // so ist die Reihenfolge in der Liste gleich der in der Matrix if (mat[i][j]) { Edge* newedge = new Edge; newedge->init(mat[i][j],j,lst[i].EIK); lst[i].EIK=newedge; } } } } void CoutAdjLst(Node lst[ANZ_KNOTEN]) { cout << "Adjazenzliste:" << endl; for(int i=0;iEKn << " mit Gewicht " << ptr->cost << endl; ptr=ptr->NKa; } } } int dfscount=1; // globaler Zähler für die Tiefensuche // muss bei weiterer Tiefensuche neu initialisiert werden // dito die "dfsnr" in der "AdjLst" void Tiefensuche(int i) { // Schema: falls Knoten i noch nicht markiert ist, // markiere Knoten i und führe die Tiefensuche von allen Nachfolgern aus aus. if (!AdjLst[i].dfsnr) { // noch nicht markierte Knoten haben noch dfsnr=0 AdjLst[i].dfsnr=dfscount++; Edge* ptr = AdjLst[i].EIK; while (ptr) { Tiefensuche(ptr->EKn); ptr=ptr->NKa; } } } void CoutDFSNumbers(Node lst[ANZ_KNOTEN]) { cout << "DFS-Nummern:" << endl; for(int i=0;ifront auf erstes int inhalt; queue *front; }; void enqueue (queue *& q, int value) { if(q==0) { q = new queue; // erstes Element in der Queue q->inhalt = value; q->front = q; // erstes und letztes Element sind identisch } else { queue * elem = new queue; elem->front = q->front; elem->inhalt = value; q->front = elem; q = elem; } } int dequeue (queue *& q) { // geht davon aus, dass die Queue q nicht leer ist // die aufrufende Funktion muss ggf. über is_empty(q) (also q==0) // testen, ob die Queue q leer ist int value = q->front->inhalt; if (q->front==q) { // ist es das letzte Element in der Queue? delete q; q=0; } else { queue *old = q->front; q->front=q->front->front; delete old; } return value; } bool is_empty (queue * q) { return q==0; } void Breitensuche(int i) { // Schema: beginne mit Knoten i // markiere Knoten i und merke in der Queue alle Nachfolger // entnehme jeweils den nächsten Knoten der Queue und merke, // falls dieser noch nicht markiert ist, alle Nachfolger in der Queue int bfscount=1; // Zähler für die Breitensuche int idx; queue* q=0; enqueue(q,i); while (!is_empty(q)) { idx=dequeue(q); if (!AdjLst[idx].bfsnr) { // noch nicht markierte Knoten haben noch bfsnr=0 AdjLst[idx].bfsnr=bfscount++; Edge* ptr = AdjLst[idx].EIK; while (ptr) { enqueue(q,ptr->EKn); ptr=ptr->NKa; } } } } void CoutBFSNumbers(Node lst[ANZ_KNOTEN]) { cout << "BFS-Nummern:" << endl; for(int i=0;i=0): // Die Knoten werden intern aufgeteilt in Baumknoten (Knoten, für die // die kürzeste Entfernung garantiert gefunden wurde), // Randknoten (Knoten, die nicht Baumknoten aber Nachfolger eines Baumknotens // sind -- für diese ist ein Weg (nicht notwendigerweise der kürzeste) gefunden), // sowie der Rest. Im Programm werden explizit nur die Randknoten verwaltet // (die Baumknoten sind die Knoten, die nicht im Rand sind und für die schon ein Weg // gefunden wurde; der Rest besteht aus den Knoten, die noch nicht erreicht wurden) // Für jeden Knoten im Rand wird sich der bisher kürzeste gefundene Weg // (genauer: dessen Entfernung) gemerkt. // Ablauf: Zu Beginn ist der Startknoten in B und dessen Nachfolger in R, gefundene // Entfernungen sind 0 zum Startknoten und das jeweilige Kantengewicht zu den Nachfolgern // In jeder Iteration wähle den Knoten aus R, dessen gefundene Entfernung minimal ist // und prüfe, ob über diesen Knoten dessen Nachfolger auf kürzerem Weg erreicht werden. // Korrektheit: Vollständige Induktion über die Anzahl der Iterationen: // Zu Beginn ist der kürzeste Weg zum Startknoten (Länge 0) gefunden. // Bei jeder Auswahl des Knotens mit minimaler Entfernung aus R ist diese die // garantiert kürzeste, weil über andere Knoten (Entfernung zu diesen ist mindestens // genauso groß) und weitere Kanten (Gewichte >=0) der Weg höchstens länger werden kann. // Um nicht nur die kürzesten Entfernungen zu bestimmen, sondern auch den zugehörigen // Weg angeben zu können, kann man einen Kürzeste-Wege-Baum berechnen lassen. // Dazu speichert man sich jedes Mal, wenn ein Knoten in den Baum eingefügt wurde // oder sich die gefundene Entfernung verringert hat, den Knoten, über den dieser // kürzere Weg gefunden wurde (= der zuletzt über delete_min aus R entnommene Knoten) // (-> Array int pred[ANZ_KNOTEN]) -- Beispiel: Führt der kürzeste Weg von // 0 nach 3 über die Knoten 6, 2 und 5, so wurde beim für den Knoten 6 sich der // Vorgänger 0 gemerkt, für den Knoten 2 der Vorgänger 6, für Knoten 5 der Vorgänger 2 // und für den Knoten 3 der Vorgänger 5. Die Vorgänger können nun ausgehend vom Knoten 3 // bestimmt werden (0 hat keinen Vorgänger, z.B. == -1) (also 3,5,2,6,0) und dann rückwärts // ausgegeben werden -- Aufgabe: Implementieren Sie die Berechnung und Ausgabe der Wege // 1. Variante: Verwaltung der Randmenge R als Liste (struct RLst) // delete_min (Suche des Knotens mit minimaler Entfernung in R): // -> Liste durchlaufen: Aufwand O(n) // insert (Einfügen eines neuen Knotens in R): // -> Vorne in der Liste einfügen: Aufwand O(1) // decrease_key (Verringern der Entfernung eines Knotens, der schon in R ist): // -> direkter Zugriff auf das Element in der Liste: Aufwand O(1) // Gesamtaufwand: n*delete_min + m*(insert oder decrease_key) = O(n^2) struct RLst_Node { int node; // Index im Knotenarray int d; // Entfernung zum Startknoten RLst_Node* next; // nächstes Element in der Liste }; struct RLst { RLst_Node* whereinR[ANZ_KNOTEN]; // Verweis, wo der Knoten in der Liste sich befindet RLst_Node* lst; // erstes Element in der Liste void init() { lst=0; for(int i=0;inode = node; elem->d = d; elem->next = lst; lst = elem; whereinR[node]=lst; } int delete_min() { // gibt Index des Knotens mit kleinstem Wert d in lst zurück // kleinstes Element finden RLst_Node *min = lst; // Zeiger auf Listenelement mit bisher kleinstem Wert RLst_Node *ptr = lst->next; // Pointer zum Durchlaufen der Liste while (ptr) { if (ptr->d < min->d) min=ptr; // Minimum merken ptr=ptr->next; } int rueckgabewert = min->node; // Element löschen if (min==lst) { // erstes und/oder einziges Element lst=lst->next; delete min; whereinR[rueckgabewert]=0; } else { // Vorgänger von min finden und min rauslöschen ptr=lst; while (ptr->next!=min) ptr=ptr->next; ptr->next=min->next; delete min; whereinR[rueckgabewert]=0; } // Index zurückgeben return rueckgabewert; } void decrease_key(int node, int d) { // verringert den Wert von Knoten node auf d whereinR[node]->d=d; } void debug() { cout << "R="; RLst_Node* ptr=lst; while (ptr) { cout << " " << ptr->node << ":" << ptr->d; ptr=ptr->next; } cout << endl; } }; // 2. Variante: Verwaltung der Randmenge R als Heap (struct RHeap) // delete_min (Suche des Knotens mit minimaler Entfernung in R): // -> in der Wurzel des Heaps, Heapeigenschaft wieder herstellen: Aufwand O(log(n)) // insert (Einfügen eines neuen Knotens in R): // -> Am Ende vom Heap einfügen und aufsteigen lassen: Aufwand O(log(n)) // decrease_key (Verringern der Entfernung eines Knotens, der schon in R ist): // -> Element im Heap aufsteigen lassen: Aufwand O(log(n)) // Gesamtaufwand: n*delete_min + m*(insert oder decrease_key) = O(m*log(n)) // In Straßengraphen ist m<=c*n -> Aufwand mit Heaps deutlich geringer als mit Listen struct RHeap { int whereinR[ANZ_KNOTEN]; // Index, an dem sich der Knoten im Heap befindet int last; // letzter Index, der zum Heap gehört struct { int node; int d; } heap[ANZ_KNOTEN]; // min-Heap!: Vater <= Soehne bzgl. der d-Werte void init() { last=-1; for(int i=0;i0 && heap[vater].d > d) { // sohn = Index der "freien Stelle" // wenn sohn==0, dann gibt es keinen Vater, der noch runterrutschen kann heap[sohn].node=heap[vater].node; heap[sohn].d =heap[vater].d ; whereinR[heap[vater].node]=sohn; sohn=vater; vater=(sohn-1)/2; } heap[sohn].node=node; heap[sohn].d=d; whereinR[node]=sohn; } int delete_min() { // gibt Index des Knotens mit kleinstem Wert d im Heap zurück // kleinstes Element ist in der Wurzel, dieses löschen und Element von // Position last auf die Wurzel kopieren und in Heap[0..--last] einsinken lassen int rueckgabewert = heap[0].node; whereinR[rueckgabewert]=-1; int node=heap[last].node; int d=heap[last].d; // (node,d) sinkt ein last--; // Heapgröße um 1 verkleinert int vater=0; int links=2*vater+1; int rechts=links+1; while (links<=last) { // es existiert noch mind. 1 Kind if (rechts<=last) { // beide Kinder existieren if (heap[links].d>heap[rechts].d) links=rechts; // heap[links].d ist jetzt der "kürzere" Sohn if (heap[links].d0 && heap[vater].d > d) { // sohn = Index der "freien Stelle" // wenn sohn==0, dann gibt es keinen Vater, der noch runterrutschen kann heap[sohn].node=heap[vater].node; heap[sohn].d =heap[vater].d ; whereinR[heap[vater].node]=sohn; sohn=vater; vater=(sohn-1)/2; } heap[sohn].node=node; heap[sohn].d=d; whereinR[node]=sohn; } void debug() { cout << "R="; for(int i=0;i<=last;i++) cout << " " << heap[i].node << ":" << heap[i].d; cout << endl; } }; void Dijkstra(Node lst[ANZ_KNOTEN], int s) { // berechnet die kürzesten Entfernungen // vom Knoten s zu allen anderen Knoten int d[ANZ_KNOTEN]; // Entfernungen von s zu . int u,v; // Knoten-Indizes int c; // Weglänge Edge* nachfolger; const int infty=ANZ_KNOTEN*10; // 10>größtes Gewicht, maximal ANZ_KNOTEN-1 Kanten pro Weg // Initialisierung for (int i=0;iEKn; c = d[u]+nachfolger->cost; // Weglänge nach v über u if (cNKa; } } // Ausgabe cout << "Errechnete Entfernungen:" << endl; for (int i=0;i