Κατ οίκον Εργασία 5 Σκελετοί Λύσεων Άσκηση 1 (α) Ο αλγόριθµος χρησιµοποιεί τη διαδικασία DFS(v) η οποία, ως γνωστό, επισκέπτεται όλους τους κόµβους που είναι συνδεδεµένοι µε τον κόµβο v. Για να µετρήσουµε τον αριθµό συνεκτικών υπογράφων ενός γράφου πρέπει να µετρήσουµε πόσες φορές χρειάζεται να καλέσουµε τη διαδικασία DFS µέχρι να επισκεφθούµε όλους τους κόµβους του γράφου: CountComponents(struct graph *G){ int Visited[n]; int components = 0; for(i=1; i size; i++) Visited[i] = 0; for(i=1; i size; i++) if (Visited[i]== 0) DFS(i, G, Visited); components++ (β) Γνωρίζουµε ότι κάθε συνεκτικός γράφος µε n κορυφές πρέπει να έχει n-1 ακµές για να είναι άκυκλος. Μπορούµε να γενικεύσουµε την παρατήρηση ως εξής: ένας γράφος µε n κορυφές και m ξεχωριστούς συνεκτικούς υπογράφους έχει n-m ακµές για να είναι άκυκλος. Άρα, αν µας δοθεί γράφος G µπορούµε (i) να εντοπίσουµε εύκολα τον αριθµό των κορυφών του, έστω k, (ii) να χρησιµοποιήσουµε την πιο πάνω διαδικασία για να µετρήσουµε τον αριθµό των συνεκτικών του υπογράφων του, έστω s και (iii) να απαντήσουµε πως για να µετατρέψουµε τον γράφο µας σε άκυκλο γράφο πρέπει να αφαιρέσουµε k-s ακµές. (γ) Για αφαίρεση των περιττών ακµών η DFS επεκτείνεται ως εξής: DFS(int v, struct graph *X, int Visited[]){ Visited[v] = 0; p = q = head[v]; while (p!= NULL){ w = p-> vertex; if (Visited[w]==0) DFS(w); p = p->next; q = p; else if (p == head[v]) head[v] = p->next; free(p); p = p->next; else q->next = p->next; free(p); p = q->next;
Άσκηση 2 Οι πιο κάτω αλγόριθµοι υπολογίζουν τα βραχύτερα µονοπάτια από τον κόµβο v στον κόµβο u µέσα στο γράφο G στα τρία σενάρια της άσκησης. (α) Το βάρος του ελάχιστου µονοπατιού σε αυτή την περίπτωση είναι ο αριθµός κόµβων που υπάρχουν στο ελάχιστο µονοπάτι ανάµεσα στους δύο κόµβους, το οποίο µπορεί να υπολογιστεί µε µια κατά πλάτος διερεύνηση στον γράφο. Εποµένως, εφαρµόζουµε κατά πλάτος διερεύνηση από τον κόµβο εκκίνησης φυλάγοντας για κάθε κόµβο το επίπεδο/ελάχιστη απόσταση αυτού από τον κόµβο εκκίνησης και µόλις συναντήσουµε τον κόµβο προορισµού για πρώτη φορά επιστρέφουµε την απόσταση την οποία έχουµε υπολογίσει: int PathLength1(graph G, vertex v, vertex u){ Q=MakeEmptyQueue(); for each w in G Visited[w]=False; Visited[v]= True; Enqueue((v,0),Q); while (!IsEmpty(Q)){ (w,n) = Dequeue(Q); if (w == u) return n; για κάθε γείτονα w του w if (Visited[w ]=False) Visited[w ]=True; Enqueue((w,n+1),Q); Χρόνος Εκτέλεσης: Ο( V + E ) για υλοποίηση µε λίστες γειτνίασης και Ο( V 2 ) για υλοποίηση µε πίνακες γειτνίασης. (β) Έστω v=v 1, v 2,, v n-1, v n = u το ελάχιστο µονοπάτι ανάµεσα στους κόµβους v και u. Παρατηρούµε πως από όλους τους γείτονες του u, o v n-1 πρέπει να είναι αυτός που βρίσκεται στη µικρότερη απόσταση από τον v. Εποµένως, σε αυτή την περίπτωση εφαρµόζουµε παραλλαγή του προηγούµενου αλγόριθµου µε τη διαφορά ότι αντί να φυλάγουµε πλήθος κορυφών µονοπατιών φυλάγουµε αποστάσεις κόµβων από τον κόµβο εκκίνησης. Επίσης, αντί της βοηθητικής δοµής ουρά χρησιµοποιούµε σωρό έτσι ώστε σε κάθε επανάληψη του βρόχου να επιλέγουµε την κοντινότερη κορυφή προς την κορυφή εκκίνησης. Πιο κάτω γράφουµε c[v] για το βάρος της κορυφής v. int PathLength2(graph G, vertex v, vertex u){ heap Q; for each w in G Visited[w]=False; Visited[v]= True; Insert((v,c[v]),Q); while (!IsEmpty(Q)){ (w,n) = DeleteMin(Q); if (w==u) return n; για κάθε γείτονα w του w if (Visited[w ]=False) Visited[w ]=True; Insert((w,n+c[w ]),Q);
Χρόνος Εκτέλεσης: Ο( V log V + E ) για υλοποίηση µε λίστες γειτνίασης και Ο( V log V + V 2 ) για υλοποίηση µε πίνακες γειτνίασης. (γ) Εφαρµόζουµε παραλλαγή του αλγόριθµου του Dijkstra µε τη διαφορά ότι στη µέτρηση των αποστάσεων λαµβάνουµε υπόψη µας και τα βάρη των κορυφών. int PathLength3(graph G, vertex v, vertex u){ heap Q; for all w in G d[w]= ; d[v]=c[v]; S= ; Q=V; while (!IsEmpty(Q)) w=deletemin(q); S=S {w; για κάθε γείτονα w του w if ( d[w ] > d[w] + c(w,w ) + c[w ]) d[w ]= d[w] + c(w,w ) + c[w ]; return d[u]; Χρόνος Εκτέλεσης: Ο( V log V + E ) Άσκηση 3 (i) Το πρόβληµα µπορεί να µοντελοποιηθεί ως πρόβληµα γράφων ως εξής: Θεωρήστε τον γράφο µε βάρη G=(V,E) όπου V={ s 1,..., sn, δηλαδή υπάρχει µία κορυφή για κάθε µάθηµα του τµήµατος Ξένων Γλωσσών και Ε={( s, s ) αν το µάθηµα s i είναι προαπαιτούµενο του µαθήµατος s j i j Η δοµή που θα χρησιµοποιήσουµε για την υλοποίηση του γράφου είναι: struct graph{ int matrix[max][max]; int size; (ii) Η διαδικασία είναι µια παραλλαγή του αλγόριθµου τοπολογικής ταξινόµησης η οποία αντί να υπολογίζει τη σειρά µε την οποία µπορούν να τύχουν παρακολούθησης τα µαθήµατα υπολογίζει τον ελάχιστο αριθµό εξαµήνων τα οποία χρειάζονται για την παρακολούθηση των µαθηµάτων. Σε κάθε επανάληψη «αφαιρεί» από το σύνολο των µαθηµάτων όλα τα µαθήµατα τα οποία είναι δυνατό να παρακολουθήσει ο φοιτητής και αυξάνει κατά ένα τα εξάµηνα παρακολούθησης. ComputeNumberOfSemesters(graph G){ int num_of_semesters = 0; int I[G->size]; I[u] = 0;
for (v = 0; v < G->size; v++) if G->matrix[u,v]==1 I[v]++; if (I[u]==0) Enqueue(u, Q); while (! IsEmpty(Q)) { while (! IsEmpty(Q)) { u = Dequeue(Q); Ι[u]-- //Ι[u] = -1 Για να µην ξαναχρησιµοποιηθεί for (v = 0; v < G->size; v++) if (G->matrix[u,v]==1) { I[v]--; num_of_semesters++ // Εισαγωγή των µαθηµάτων που γίνονται διαθέσιµα if (I[u]==0) Enqueue(u, Q); return num_of_semesters; (iii) Γράφος που µοντελοποιεί το πρόβληµα: Γ 5 Γ 1 Γ 6 Γ 3 Γ 4 Γ 7 Γ 2 Πίνακας Γειτνίασης: 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0
Αρχικά το πρόγραµµα υπολογίζει τον βαθµό εισόδου για κάθε κόµβο µε αποτέλεσµα να δηµιουργηθεί ο πιο κάτω πίνακας Ι 1 1 0 1 3 0 2 Στη συνέχεια ο αλγόριθµος τοποθετεί στην ουρά τους κόµβους µε βαθµό εισόδου 0, δηλαδή τους κόµβους 3 και 6. Στο τελευταίο βήµα του αλγορίθµου έχουµε µια επανάληψη η οποία τερµατίζει (όπως και στην τοπολογική ταξινόµηση) όταν η ουρά γίνει άδεια. Κάθε τέτοια επανάληψη αυξάνει κατά ένα τον αριθµό των εξαµήνων που χρειάζονται για την παρακολούθηση των µαθηµάτων. Σε κάθε επανάληψη «παρακολουθούνται» τα µαθήµατα που είναι διαθέσιµα και εισάγονται στην ουρά τα µαθήµατα που γίνονται διαθέσιµα. Αρχικές τιµές πίνακας I Q=[3,6] num_of_semesters = 0 1 1 0 1 3 0 2 1 η επανάληψη πίνακας I 0 0-1 0 2-1 2 Q = [1,2,4] num_of_semesters = 1 2 η επανάληψη πίνακας I -1-1 -1-1 0-1 1 Q= [5] num_of_semesters = 2 3 η επανάληψη πίνακας I -1-1 -1-1 -1-1 0 Q= [7] num_of_semesters = 3 4 η επανάληψη πίνακας I -1-1 -1-1 -1-1 -1 Q= [] num_of_semesters = 4 (iv) Η διαδικασία είναι µια παραλλαγή του αλγόριθµου του µέρους (ii) όπου αντί να παρακολουθούνται όλα τα διαθέσιµα µαθήµατα παρακολουθούνται µόνο 2. Σε αυτή την περίπτωση η λύση µας χρησιµοποιεί αναδροµή η οποία θεωρεί όλους τους διαφορετικούς τρόπους για να επιλέξουµε 2 µαθήµατα και εντοπίζει τον ελάχιστο αριθµό εξαµήνων παρακολούθησης που προκύπτει ανάµεσα σε αυτούς. ComputeNumberOfSemesters2(graph G){ int num_of_semesters = 0; int I[G->size]; I[u] = 0; for (v = 0; v < G->size; v++) if G->matrix[u,v]==1 I[v]++; CNS(G, I);
CNS(graph G, int I[]){ x = ; if (I[u]==0) Enqueue(u, Q); if (Q->size == 0); x = 0; if (Q->size == 1) v = Dequeue(Q); Ι[v]--; for (i = 0; i < G->size; i++) if (G->matrix[v,i]==1) I[i]--; x = CNS(G, I); else for (v = 1; v <= Q->size; v++) for (u = v+1; u <= Q->size; u++) { Ι[u]--; Ι[v]--; for (i = 0; i < G->size; i++) if (G->matrix[v,i]==1) I[i]--; if (G->matrix[u,i]==1) I[i]--; x = min (x,cns(g, I)); Ι[u]++; Ι[v]++; return x+1;