ΠΑΝΕΠΙΣΤΗΜΙΟ ΚΥΠΡΟΥ ΤΜΗΜΑ ΠΛΗΡΟΦΟΡΙΚΗΣ ΕΠΛ 231: Δομές Δεδομένων και Αλγόριθμοι Εαρινό Εξάμηνο 2013 ΑΣΚΗΣΗ 4 Σωροί, Γράφοι Διδάσκων Καθηγητής: Παναγιώτης Ανδρέου Ημερομηνία Υποβολής: 05/04/2013 Ημερομηνία Παράδοσης: 19/04/2013 ΠΕΡΙΓΡΑΦΗ Σε αυτή την άσκηση καλείστε να αναλύσετε και να δημιουργήσετε δομές δεδομένων και αλγόριθμους για κάποια προβλήματα. Επιπλέον καλείστε να υπολογίσετε το χρόνο εκτέλεσης των αλγορίθμων που θα δημιουργήσετε. Άσκηση 1 (30 μονάδες) Ένας d σωρός είναι παρόμοιος με ένα δυαδικό σωρό με τη διαφορά ότι κάθε κόμβος αντί δύο έχει d παιδιά. Α. Πως μπορεί να υλοποιηθεί ένας d σωρός χρησιμοποιώντας ένα πίνακα; Β. Ποιο είναι το ύψος ενός d σωρού με n στοιχεία σαν συνάρτηση των n και d; Γ. Να υλοποιήσετε τις διαδικασίες DeleteMin και Insert για τη δομή δεδομένων d σωρός και να αναλύσετε τον χρόνο εκτέλεσης των διαδικασιών σας. Δ. Να υλοποιήσετε τη διαδικασία ΙncreaseKey(A, i, k) η οποία αυξάνει το κλειδί του σωρού Α[i] κατά k και αναπροσαρμόζει τον σωρό Α κατάλληλα. A. Ένας d σωρός υλοποιείται παρόμοια με ένα δυαδικό σωρό χρησιμοποιώντας ένα πίνακα όπου: στη θέση 1 βάζουμε το στοιχείο της ρίζας, αν κάποιος κόμβος u βρίσκεται στη θέση i, τότε τοποθετούμε τα παιδιά του κόμβου στις θέσεις (i 1).d + 2 μέχρι και i.d +1 του πίνακα
o πατέρας του κόμβου που βρίσκεται στη θέση i βρίσκεται στη θέση 1. B. Στο l επίπεδο ενός d αδικού δέντρου, μπορεί να υπάρχουν το πολύ d l 1 κόμβοι. Σε ένα πλήρες d αδικό δέντρο ύψους h όλα τα επίπεδα μέχρι το (h 1) οστό είναι εντελώς γεμάτα, και το επίπεδο h είναι γεμάτο από τα αριστερά στα δεξιά. Ο αριθμός των κόμβων μέχρι βάθος h 1 δίνεται από επομένως, ένα πλήρες δέντρο ύψους h έχει μεταξύ 1 και 1 κόμβους. Αφού 1 1, παίρνοντας λογάριθμους στα δύο σκέλη της ανισότητας καταλήγουμε στο συμπέρασμα ότι Γ. Η εισαγωγή στοιχείου σε ένα d σωρό γίνεται όπως ακριβώς σε ένα δυαδικό σωρό: το στοιχείο που θέλουμε να εισαγάγουμε τοποθετείται αρχικά στην θέση size+1 του πίνακα και μετά γίνεται αναρρίχηση του στοιχείου μέχρι να ικανοποιηθεί η ιδιότητα σειράς του σωρού. H εξαγωγή του ελάχιστου στοιχείου γίνεται επίσης βάση της κύριας ιδέας του αλγόριθμου δυαδικού σωρού. Θεωρούμε ότι η υλοποίηση d σωρού γίνεται όπως για ένα δυαδικό σωρό με τον τρόπο που περιγράφεται στο μέρος (Α) χρησιμοποιώντας την πιο κάτω δομή. public class DHeap { private int contents[]; private int size; private int maxsize; private final int d; public DHeap(int n, int d) { this.contents = new int[n]; this.size = 0; this.maxsize = n 1; this.d = d;
Οι ζητούμενες διαδικασίες έχουν ως εξής: public void insert(int k) { if (size < maxsize) { int index = size + 1; while(index>1 && contents[(((index 2)/d)+1)]>k){ contents[index]=contents[(((index 2)/d)+1)]; index = ((index 2) / d)+1; contents[index] = k; size++; contents[0] = size; public int deletemin() { int min = 0, last; int x = 1, child = 0; int min_child = 0; int max_child = 0; if (!isempty()) { min = contents[1]; last = contents[size]; size ; contents[0] = size; while (((x 1)*d + 2) <= size) { min_child = (x 1)*d + 2; max_child = x*d+1; child = min_child; for(int i=min_child; i<max_child; i++) { if(contents[i]<contents[child]) child=i; if (last > contents[child]) { contents[x] = contents[child]; x = child; else break; contents[x] = last; return min;
Ο χρόνος εκτέλεσης της Insert είναι ανάλογος του ύψους του δέντρου, δηλαδή,. Ο χρόνος εκτέλεσης της deletemin εξαρτάται από τον αριθμό επαναλήψεων του κύριου while loop και του χρόνου εκτέλεσης της κάθε επανάληψης. Ο αριθμός επαναλήψεων είναι προφανώς στη χείριστη περίπτωση ανάλογος του ύψους του δέντρου. Ο χρόνος εκτέλεσης του βρόχου εξαρτάται από τον χρόνο που απαιτείται για εύρεση του παιδιού με το ελάχιστο στοιχείο. Προφανώς για αυτό χρειαζόμαστε χρόνο της τάξης Ο(d), επομένως ο ολικός χρόνος εκτέλεσης της DeleteMin είναι.. Δ. H ζητούμενη διαδικασία πρώτα αυξάνει κατάλληλα το κλειδί i του σωρού, και μετά εκτελεί την διαδικασία PercoladeDown στη θέση αυτή (κατάλληλα διασκευασμένη για d σωρούς) έτσι ώστε να αναπροσαρμόσει κατάλληλα τον σωρό. Άσκηση 2 (20 μονάδες) Το τετράγωνο ενός γράφου G = (V,E) είναι ο γράφος G = (V, E ) όπου η ακμή (v,w) E αν και μόνο αν υπάρχει κορυφή u τέτοια ώστε (v,u) E και (u,w) Ε. Δηλαδή, ο γράφος G περιέχει ακμή από την κορυφή v στην κορυφή w αν η απόσταση μεταξύ των κορυφών v και w στο γράφο G είναι 2. Να γράψετε δύο αποδοτικούς αλγόριθμους οι οποίοι με δεδομένο εισόδου ένα γράφο να υπολογίζουν το τετράγωνό του. Ο πρώτος αλγόριθμος να υποθέτει υλοποίηση γράφων με πίνακα γειτνίασης, και ο δεύτερος αλγόριθμος να υποθέτει υλοποίηση γράφων με λίστα γειτνίασης. Να αναλύσετε τον χρόνο εκτέλεσης των αλγόριθμών σας. O ζητούμενος αλγόριθμος έχει ως εξής: Για κάθε κόμβο βρίσκουμε τους γείτονές του και προσθέτουμε τους γείτονες τους (των γειτόνων) ως γείτονες του κόμβου στο νέο γράφο. (α) Υλοποίηση με πίνακα γειτνίασης Χρησιμοποιούμε τη δομή: class graph{ int table[maxsize][maxsize]; int size; Έστω ο πίνακας γειτνίασης του γράφου. Ο πιο κάτω κώδικας υπολογίζει το τετράγωνο του γράφου G, G square(graph G, graph G ){ G.size = G.size; for (u=0; u <= G.size 1; u++)
for ( v=0; v <= G.size 1; v++) if (G.table[u][v] == 1) for (w=0; w <= G.size 1; w++) if (G.table[v][w]==1 && u!=w) G.table[u][w]= 1; Ο χρόνος εκτέλεσης του αλγόριθμου είναι Ο( V 3 ), όπου V είναι ο αριθμός κορυφών του γράφου. (β) Υλοποίηση με λίστα γειτνίασης Χρησιμοποιούμε τις δομές: class graph{ class node{ node table[maxsize]; int vertex; int size; node next; όπου ο πίνακας table στη θέση i περιέχει συνδεδεμένη λίστα με όλες τις κορυφές προς τις οποίες υπάρχει ακμή από την κορυφή i. Υποθέτουμε πως κάθε κόμβος της συνδεδεμένης λίστας είναι τύπου node, όπου το node είναι εγγραφή με δύο πεδία: vertex, το οποίο δίνει το όνομα του κόμβου και next, το οποίο δείχνει τον επόμενο κόμβο μέσα στη λίστα. Έστω ο πίνακας γειτνίασης του γράφου. Ο πιο κάτω κώδικας υπολογίζει το τετράγωνο του γράφου G, G square(graph G, graph G ){ G.size = G.size; for ( i=0; i<=g.size 1; i++ ) G.table[i] = NULL; for ( i = 0; i <= G. size 1; i++ ){ p = G.table[i]; while (p!= NULL){ u = G.table[p.vertex]; while (u!= NULL){ if (i!= u.vertex) { w = malloc(sizeof(node)); w.vertex = u.vertex; w.next = G.table[i]; G.table[i] = w; u = u.next; p = p >next; O χρόνος εκτέλεσης του αλγόριθμου είναι Ο( V 3 ).
Άσκηση 3 (20 μονάδες) Ένας κατευθυνόμενος μη κυκλικός γράφος ονομάζεται σύνδεσμος αν και μόνο αν, (1) υπάρχει κόμβος του γράφου από τον οποίο υπάρχουν μονοπάτια προς όλους τους υπόλοιπους κόμβους του γράφου και (2) υπάρχει κόμβος του γράφου προς τον οποίο υπάρχουν μονοπάτια από όλους του υπόλοιπους κόμβους του γράφου. Α. (α) Να δώσετε ένα παράδειγμα γράφου που είναι σύνδεσμος και ένα παράδειγμα γράφου που δεν είναι σύνδεσμος. Β. (β) Να γράψετε αλγόριθμο ο οποίος με δεδομένο εισόδου ένα γράφο υλοποιημένο με πίνακα γειτνίασης αποφασίζει κατά πόσο είναι σύνδεσμος και να υπολογίσετε τον χρόνο εκτέλεσης του αλγόριθμού σας. A. σύνδεσμος μη σύνδεσμος Έστω άκυκλος γράφος G = (V,E). Για να είναι o G σύνδεσμος θα πρέπει I. να περιέχει κάποιο κόμβο, v, από τον οποίο υπάρχουν μονοπάτια προς όλους τους άλλους κόμβους του V, και II. να περιέχει κάποιο κόμβο u προς τον οποίο να υπάρχουν μονοπάτια από όλους τους άλλους κόμβους του V. Παρατηρούμε ότι, αφού ο G είναι άκυκλος, ο κόμβος v δεν θα πρέπει να δέχεται ακμή από κανένα από τους υπόλοιπους κόμβους του γράφου, και ο κόμβους u δεν θα πρέπει να έχει καμιά ακμή προς τους υπόλοιπους κόμβους του γράφου. Επιπλέον οι κορυφές u και v θα πρέπει να είναι οι μοναδικές κορυφές που ικανοποιούν τις πιο πάνω ιδιότητες. Επομένως ο ζητούμενος αλγόριθμος έχει ως εξής: (i) (ii) Βρες όλους τους κόμβους προς τους οποίους δεν υπάρχει ακμή από κανένα άλλο κόμβο του γράφου. Αν το πλήθος τους είναι άνισο με 1 τότε απάντησε ότι ο γράφος δεν είναι σύνδεσμος. Διαφορετικά, εκτέλεσε τη διαδικασία DFS από τον κόμβο που επιστράφηκε στο βήμα 1. Αν όλοι οι υπόλοιποι κόμβοι είναι προσιτοί από τον κόμβο αυτό τότε συνέχισε στο βήμα (iii), διαφορετικά απάντησε ότι ο γράφος δεν είναι σύνδεσμος.
(iii) (iv) (v) Βρες όλους τους κόμβους από τους οποίους δεν υπάρχει ακμή προς κανένα άλλο κόμβο του γράφου. Αν το πλήθος τους είναι άνισο με 1 τότε απάντησε ότι ο γράφος δεν είναι σύνδεσμος. Διαφορετικά, δημιούργησε τον αντίστροφο γράφο του G, και Eκτέλεσε μια DFS από τον κόμβο που επιστράφηκε στο βήμα (iii). Αν όλοι οι υπόλοιποι κόμβοι είναι προσιτοί από τον κόμβο αυτό απάντησε ότι ο γράφος είναι σύνδεσμος, διαφορετικά ότι ο γράφος δεν είναι σύνδεσμος. Β. Υλοποίηση με πίνακα γειτνίασης. Χρησιμοποιούμε τη δομή: class graph{ int table[maxsize][maxsize]; int size; Για το βήμα (i) θα πρέπει να διαβάσουμε μια μια τις στήλες του πίνακα και να επιστρέψουμε αυτές που έχουν μόνο 0. Χρόνος Εκτέλεσης Ο( V ²) Τα βήματα (ii) και (v) υλοποιούνται ως εξής DepthFirstSearch(graph1 G, int v){ for (w=1; w <= G.size; w++) visited[w] = 0; DFS(G, visited, v); for (w=1; w <= G.size; w++) if (Visited[w]== False) return FALSE; //proceed to next step DFS(struct graph1 G, int visited[max], int v){ visited[v] = 1; for (w=1; w <= G.size; w++) if (G.table[v,w] == 1 && visited[w]==0) DFS(G, visited, w) Χρόνος Εκτέλεσης Ο( V ²)
Για το βήμα (iii) θα πρέπει να διαβάσουμε μια μια τις γραμμές του πίνακα και να επιστρέψουμε αυτές που έχουν μόνο 0. Χρόνος Εκτέλεσης Ο( V ²) Το βήμα (iv) έχει υλοποιηθεί στο Φροντιστήριο 9, Άσκηση 2. Χρόνος Εκτέλεσης Ο( V ²) Το βήμα (v) αποτελεί επανάληψη του βήματος (ii). Χρόνος Εκτέλεσης Ο( V ²) Επομένως ο χρόνος εκτέλεσης του αλγόριθμου είναι της τάξης Ο( V ²). Άσκηση 4 (30 μονάδες) Ένας μη κατευθυνόμενος γράφος G(V, E) ονομάζεται διμερής (bipartite), αν το σύνολο των κόμβων του V μπορεί να χωριστεί σε V 1 V, V 2 V, έτσι ώστε V 1 V 2 =V, V 1 V 2 = και e(u,v) E ισχύει u V 1 και v V 2. Α. Να δώσετε ένα παράδειγμα γράφου που είναι διμερής Β. Να δώσετε ένα παράδειγμα γράφου που ΔΕΝ είναι διμερής. Γ. Να γράψετε αλγόριθμο ο οποίος με δεδομένο εισόδου ένα γράφο να αποφασίζει κατά πόσο είναι διμερής. Eπίσης, να υπολογίσετε τον χρόνο εκτέλεσης του αλγορίθμου σας σε υλοποίηση με πίνακα γειτνίασης και λίστα γειτνίασης. Α. v 1 v 3 Β. v 2 v 4 v 1 v 3 v 2 v 4 Γ.
Μας δίνεται ένας γράφος G και θέλουμε να αποφασίσουμε κατά πόσο είναι διμερής. Για να το πετύχουμε, επιχειρούμε δημιουργία διάσπασης των κορυφών του σε δύο υποσύνολα, V 1 και V 2, έτσι ώστε καμία ακμή να μην έχει και τις δύο κορυφές της στο ίδιο υποσύνολο ως εξής: Ξεκινούμε με κάποιο τυχαίο κόμβο και τον τοποθετούμε στο σύνολο V 1. Στη συνέχεια παίρνουμε όλους τους γείτονές του και τους τοποθετούμε στο σύνολο V 2. Συνεχίζουμε με τους γείτονες αυτών και τους τοποθετούμε στο σύνολο 1, και ούτω καθεξής. Αν συναντήσουμε κάποιο κόμβο ο οποίος ανήκει ήδη σε κάποιο από τα δύο σύνολα υπάρχουν δύο περιπτώσεις: αν βρίσκεται στο σύνολο όπου επιθυμούμε να τον τοποθετήσουμε και στην παρούσα φάση, τότε προχωρούμε κανονικά, αν όμως ανήκει στο άλλο σύνολο, συμπεραίνουμε ότι ο γράφος μας δεν είναι διμερής και τερματίζουμε την εκτέλεση της διαδικασίας. Ο αλγόριθμος μπορεί να υλοποιηθεί ως εξής: θεωρούμε σε ένα γράφο G που περιλαμβάνει V κόμβους και Ε ακμές. Μπορούμε να πραγματοποιήσουμε μια διάσχιση BFS για να επισκεφθούμε όλους τους κόμβους. Στη συνέχεια όλους τους κόμβους που έλαβαν περιττή αρίθμηση κατά τη διάσχιση τους τοποθετούμε στο υποσύνολο V 1 και όσους έλαβαν άρτια αρίθμηση τους τοποθετούμε στο υποσύνολο V 2. Έπειτα ελέγχουμε κάθε ακμή για να δούμε ότι συνδέει κόμβο του V 1 με κόμβο του V 2. Ο αλγόριθμος μπορεί να υλοποιηθεί ως μια παραλλαγή του BFS. Ο πίνακας partition χρησιμοποιείται για να εκφράσει το υποσύνολο στο οποίο ανήκει ένας κόμβος και οι θέσεις του θα έχουν την τιμή 1 ή 2. int isbipartite (graph G, vertex v) { Q = MakeEmptyQueue(); for each w in G { visited[w] = False; // αρχικοποίηση των θέσεων // του πίνακα partition partition[w] = 0; visited[v] = True; partition[w] = 1; Enqueue(v,Q); while (!IsEmpty(Q)){ w = Dequeue(Q); Visit(w); for each u adjacent to w // αν βρεθούν κόμβοι που ανήκουν στο // ίδιο υποσύνολο ο γράφος δεν είναι // διμερής if (partition[u] == partition[w])
return 1; return 0; else if (visited[u]==false) { visited[u]=true; partition[u] = 3 partition[w]; Enqueue(u,Q); Για την περίπτωση υλοποίησης με λίστες γειτνίασης η συγκεκριμένη λύση έχει χρόνο εκτέλεσης Ο( V + Ε ), αφού απαιτείται χρόνος Ο( V ) για να εισαχθεί κάποιος κόμβος στο σύνολο V1 ή V2 και χρόνος Ο( Ε ) για τον έλεγχο των ακμών, δηλ. στο σύνολο Ο( V + Ε ). Στην υλοποίηση με πίνακες γειτνίασης ο χρόνος εκτέλεσης γίνεται Ο( V 2 ), όπως ισχύει και για τη BFS διάσχιση δηλ.