ΠΑΝΕΠΙΣΤΗΜΙΟ ΚΥΠΡΟΥ ΤΜΗΜΑ ΠΛΗΡΟΦΟΡΙΚΗΣ ΕΠΛ 231: Δομές Δεδομένων και Αλγόριθμοι Εαρινό Εξάμηνο 2013 ΘΕΩΡΗΤΙΚΗ ΑΣΚΗΣΗ 2 ΛΥΣΕΙΣ Γραμμικές Δομές Δεδομένων, Ταξινόμηση Διδάσκων Καθηγητής: Παναγιώτης Ανδρέου Ημερομηνία Υποβολής: 22/02/2013 Ημερομηνία Παράδοσης: 08/03/2013 ΠΕΡΙΓΡΑΦΗ Σε αυτή την άσκηση καλείστε να αναλύσετε και να δημιουργήσετε δομές δεδομένων και αλγόριθμους για κάποια προβλήματα. Επιπλέον καλείστε να υπολογίσετε το χρόνο εκτέλεσης των αλγορίθμων που θα δημιουργήσετε. Άσκηση 1 (10 μονάδες) Να υπολογίσετε τους χρόνους εκτέλεσης των διαδικασιών MergeSort και QuickSort όταν κληθούν σε: Α. ταξινομημένο πίνακα, Β. πίνακα σε σειρά αντίθετη από την ζητούμενη, και Γ. τυχαίο πίνακα Διαδικασία MergeSort A. Η MergeSort σε ταξινομημένο πίνακα θα πάρει χρόνο O(n log n) γιατί η διαδικασία θα προσπαθήσει, ακόμα και αν ο πίνακας είναι ταξινομημένος, να μοιράσει τον πίνακα στα δύο και αναδρομικά να ταξινομήσει τα δύο μέρη και στο τέλος να τα συγχωνεύσει. Άρα έχουμε: Βάση της αναδρομής Τ(0) = Τ(1) = 1 Γενική περίπτωση Τ(n) = 2T(n/2) + n και λύνοντας την αναδρομική εξίσωση παίρνουμε Τ(n) O(n log n) Για το ερώτημα B. και Γ. ισχύει ο ίδιος χρόνος εκτέλεσης για τη MergeSort
Διαδικασία QuickSort Α. Σε ένα ταξινομημένο πίνακα, αν υποθέσουμε ότι το pivot στοιχείο που παίρνουμε είναι το μεσαίο στοιχείο του πίνακα, τότε, αφού ο πίνακας είναι ταξινομημένος, θα έχουμε επιλέξει το στοιχείο με την μεσαία τιμή. Έχουμε: Βάση της αναδρομής Τ(0) = Τ(1) = c Γενική περίπτωση Τ(n) = T(n/2) + T(n n/2 1) + cn = 2T(n/2) + cn και λύνοντας την αναδρομική εξίσωση παίρνουμε Τ(n) O(n log n) Β. Για την περίπτωση που ο πίνακας είναι ταξινομημένος στην αντίθετη σειρά από τη ζητούμενη έχουμε τον ίδιο χρόνο εκτέλεσης γιατί πάλι θα επιλεγεί σαν pivot το στοιχείο με τη μεσαία τιμή και η σχετική αναδρομική διαδικασία είναι η ίδια με την πιο πάνω. Γ. Για τυχαίο πίνακα, στη χείριστη περίπτωση που το μέγεθος του αριστερού κομματιού μετά την partition είναι i = 0 ή i = n 1, έχουμε την ακόλουθη αναδρομική εξίσωση. Βάση της αναδρομής Τ(0) = Τ(1) = c Γενική περίπτωση Τ(n) = T(0) + T(n 1) + cn = T(n 1) + cn Λύνοντας την πιο πάνω αναδρομική εξίσωση παίρνουμε Τ(n) O(n 2 ) Άσκηση 2 (15 μονάδες) Να περιγράψετε μία αναδρομική διαδικασία που να ελέγχει αν μία συμβολοσειρά είναι παλινδρομική ή όχι, αν δηλαδή διαβάζεται με τον ίδιο τρόπο ξεκινώντας είτε από την αρχή είτε από το τέλος της. Θεωρείστε ότι τα γράμματα της συμβολοσειράς είναι αποθηκευμένα σε μία διπλά συνδεδεμένη λίστα. Παραδείγματα παλίνδρομων: α, αα, αββα, αβγβα Τέλος, να αναλύσετε τη χρονική πολυπλοκότητα της διαδικασίας. Δομή (κλάση) Διπλά Συνδεδεμένης Λίστας class Node { class DoubleList { char data; Node head; Node next; int size; Node previous;
boolean ispalindrome() { if(size==0) return true; else{ //Βρες το τέλος της λίστας Node tmp = head; while(tmp.next!=null) tmp = tmp.next; return ispalindromeaux(head, tmp); boolean ispalindromeaux(node start, Node end) { // Aν τα στοιχεία των δύο θέσεων διαφέρουν // επιστρέφουμε ότι η λέξη δεν είναι // παλινδρομική if (start.data!= end.data) return false; // Διαφορετικά, αν οι κόμβοι της λίστας έχουν // εξαντληθεί επιστρέφουμε ότι η λέξη // είναι παλίνδρομο else if (start == end start.next == end) return true; else return ispalindromeaux(start.next, end.previous); Χρονική Πολυπλοκότητα: O(n) όπου n το μήκος της λίστας Άσκηση 3 (25 μονάδες) Να γράψετε i) μία αναδρομική; και ii) μία μη αναδρομική διαδικασία InsertAfter(L,x,i) οι οποίες να παίρνουν ως δεδομένο εισόδου μια κυκλική διπλά συνδεδεμένη λίστα L, και να εισάγουν το στοιχείο x μετά από το i οστό στοιχείο της λίστας. Να συγκρίνετε τις δύο διαδικασίες ως προς τον χρόνο εκτέλεσής τους και τον χώρο που χρησιμοποιούν. Τέλος, να αναλύσετε τη χρονική πολυπλοκότητα της κάθε διαδικασίας.
Υποθέτουμε πως οι λίστες είναι υλοποιημένες χρησιμοποιώντας τις πιο κάτω δομές. class Node{ int data; Node next; Node prev; class CDL_List{ Node top; Υποθέτουμε ότι το πεδίο top της λίστας δείχνει το πρώτο στοιχείο της λίστας. Επίσης υποθέτουμε ότι αν το i είναι μεγαλύτερο από το μέγεθος της λίστας, τότε το στοιχείο προς εισαγωγή εισάγεται στην τελευταία θέση της λίστας. A. Μη αναδρομική εκδοχή InsertAfter(CDL_List L, int x, int i){ Node p = new Node(); p.data = x; Node q = L.top; if (q == null) { p.next = p; p.prev = p; L.top = p; return; if (i == 0) { L.top = p; p.next = q; p.prev = q.prev; q.prev.next = p; q.prev = p; int j = 1; while (j<i AND q.next!= L.top) { q = q.next; j++; p.next = q.next;
p.next.prev = p; q.next = p; p.prev = q; Β. Αναδρομική εκδοχή RecInsertAfter(CDL_List L, int x, int i){ if (L >top == NULL) Node p = new Node(); p.data = x; p.next = p; p.prev = p; L.top = p; elseif (i == 0) Node p = new Node(); p.data = x; p.next = L.top; p.prev = L.top.prev; p.next.prev = p; p.prev.next = p L.top = p; else RecInsAfter(L, L.top, x, i); RecInsAfter(CDL_List L, Node q, int x, int i){ if (i == 0 OR q.next == L.top) Node p = new Node(); p.data = x; p.next = q.next; p.next.prev = p; q.next = p; p.prev = q; else RecInsAfter (L, q.next, x, i 1);
Άσκηση 4 (25 μονάδες) Έστω πίνακας T στον οποίο είναι καταγραμμένες οι βαθμολογίες της ενδιάμεσης εξέτασης του μαθήματος ΕΠΛ231, δηλαδή ο πίνακας T είναι μεγέθους n+1 (η θέση 0 δεν χρησιμοποιείται). Αν T[i]=90, τότε η βαθμολογία του φοιτητή i στην ενδιάμεση εξέταση ήταν 90. Μας ενδιαφέρει να μάθουμε, για κάθε φοιτητή j, τον μέγιστο αριθμό των προηγούμενων διαδοχικών φοιτητών, συμπεριλαμβανομένου και του j, των οποίων η βαθμολογία ήταν μικρότερη ή ίση από τη βαθμολογία του j. Πιο συγκεκριμένα, θέλουμε να δημιουργήσουμε πίνακα S, μεγέθους n+1, τέτοιο ώστε, αν S[j]=k, τότε να ισχύουν τα ακόλουθα: (1) για κάθε m στο διάστημα [j k+1, j], T[m] T[j], και (2) αν j k>0 τότε T[j k] > T[j]. Για παράδειγμα αν j 0 1 2 3 4 5 6 7 T(j) 94 82 43 67 52 76 82 Τότε j 0 1 2 3 4 5 6 7 S(j) 1 1 1 2 1 4 6 S[(j:7)] = (k:6) επειδή Τ[(j:7)]=82>={ T[ j ]: 2 j< 7 Να προτείνετε αλγόριθμο που να λύνει το πρόβλημα με γραμμικό χρόνο εκτέλεσης χρησιμοποιώντας στοίβες. Να δικαιολογήσετε την ορθότητα και τη χρονική πολυπλοκότητα του αλγορίθμου σας. Για την επίλυση του προβλήματος, επεξεργαζόμαστε μια μια τις βαθμολογίες των φοιτητών ως εξής: 1. Τοποθετούμε τη βαθμολογία του πρώτου φοιτητή σε μία στοίβα και θέτουμε S[1] = 1. 2. Για τον φοιτητή i, αφαιρούμε (pop) από τη στοίβα όλους τους φοιτητές j με μικρότερη βαθμολογία (T[i] >= T[j]). Η ζητούμενη τιμή S[i] για το φοιτητή i είναι το άθροισμα των τιμών S των φοιτητών j που βρήκαμε προηγουμένως + 1. Τοποθετούμε στη στοίβα τον καινούργιο φοιτητή. Υποθέτουμε την ύπαρξη υλοποίησης στοίβας με πράξεις MakeEmpty, IsEmpty, Push, Pop και Top. Ο αλγόριθμος έχει ως εξής:
public static void grading(int T[]) { int S[] = new int[t.length]; S[1] = 1; Stack<Integer> s = new Stack<Integer>(); s.push(1); int d; for(int i=2; i<t.length; i++) { S[i]=1; while(!s.isempty()!=0) { d = s.top(); if(t[i]>=t[d]) { s.pop(); S[i] += S[d]; else break; s.push(i); //Print S[] for(int i=1; i<s.length; i++) System.out.print(S[i] + " "); System.out.println(); O χρόνος εκτέλεσης της διαδικασίας δίνεται από το άθροισμα: 1 όπου t i είναι ο χρόνος του εσωτερικού βρόγχου while (πιο συγκεκριμένα ο αριθμός των Pop που εκτελούνται στο βρόχο while) κατά την i οστή εκτέλεσης του βρόχου for. Αφού κάθε στοιχείο εισάγεται και εξάγεται από τη στοίβα ακριβώς μια φορά έχουμε επίσης ότι: 1 Επομένως ο χρόνος εκτέλεσης της διαδικασίας είναι της τάξης Θ(n).
Άσκηση 5 (25 μονάδες) Να δώσετε ψευδοκώδικα για τον αλγόριθμο που επιλύει τη μη αναδρομική εκδοχή του αλγόριθμου ταξινόμησης MergeSort. Τέλος, να αναλύσετε τη χρονική πολυπλοκότητα της διαδικασίας. public static void iterativemergesort(int[] a) { int[] from = a, to = new int[a.length]; for (int blocksize = 1; blocksize < a.length; blocksize *= 2) { for (int start = 0; start<a.length; start += 2*blockSize) iterativemerge(from, to, start, start+blocksize, start + 2*blockSize); int[] temp = from; from = to; to = temp; if (a!= from) // copy back for (int k = 0; k < a.length; k++) a[k] = from[k]; private static void iterativemerge(int[] from, int[] to, int lo, int mid, int hi) { if (mid > from.length) mid = from.length; if (hi > from.length) hi = from.length; int i = lo, j = mid; for (int k = lo; k < hi; k++) { if (i == mid) to[k] = from[j++]; else if (j == hi) to[k] = from[i++]; else if (from[j] < from[i]) to[k] = from[j++]; else to[k] = from[i++];