ΠΑΝΕΠΙΣΤΗΜΙΟ ΚΥΠΡΟΥ ΤΜΗΜΑ ΠΛΗΡΟΦΟΡΙΚΗΣ ΕΠΛ 23: οµές εδοµένων και Αλγόριθµοι Ενδιάµεση Εξέταση Ηµεροµηνία : ευτέρα, 3 Νοεµβρίου 2008 ιάρκεια : 2.00-4.00 ιδάσκουσα : Άννα Φιλίππου Ονοµατεπώνυµο: ΣΚΕΛΕΤΟΙ ΛΥΣΕΩΝ Αριθµός Ταυτότητας: Άσκηση [20 µονάδες] Να βάλετε σε κύκλο τη σωστή απάντηση στις πιο κάτω ερωτήσεις πολλαπλής επιλογής.. Έστω σταθερές Α, k και m > 0. Τότε η πρόταση A lg k n O(n m ) είναι: α. Αληθής β. Ψευδής γ. Εξαρτάται από τις τιµές των Α και k δ. Εξαρτάται από την τιµή του m 2. Έστω f(n) o χρόνος εκτέλεσης χείριστης περίπτωσης του πιο κάτω κώδικα. sum = 0; n i n n i for ( i = ; i <= n; i++) T = = n = for ( j = ; j <= i; j++) i= j= k = i= j= for ( k = ; k <= n ; k++) n 2 n( n + ) n ( n + ) sum=sum+j; = i= 2 2 for ( i = ; i <= n²; i++) 2 2 n i n 2 2 for ( j = ; j <= i; j++) n ( n + ) T2 = = i = sum=sum+j; i= j= i= 2 4 Τότε: T = T + T2 O( n ) α. f(n) Θ(n 3 ) β. f(n) Θ(n 4 ) γ. f(n) Θ(n 7 ) δ. f(n) Θ(n 2 )
3. Θεωρείστε ένα δυαδικό δένδρο αναζήτησης στο οποίο είναι αποθηκευµένοι οι ακέραιοι - 00. Έστω ότι καλείται η διαδικασία αναζήτησης (Find) για το στοιχείο 50. Ποια από τις πιο κάτω ακολουθίες δεν θα µπορούσε να είναι έγκυρη ακολουθία κόµβων που συναντώνται κατά την αναζήτηση; α. 5, 20, 28, 32, 46 β. 40, 45, 90, 76, 68 γ. 35, 62, 90, 88 65 δ. Όλες οι πιο πάνω είναι έγκυρες ακολουθίες 35 62 Η ακολουθία (γ) µπορεί να εµφανιστεί σε δένδρα όπως αυτό του σχήµατος που προφανώς δεν είναι Α: το 50 θα έπρεπε να βρίσκεται αριστερά από τον κόµβο 62. 88 90 7 65 50 4. Για κάθε f(n) και g(n) τέτοιες ώστε f (n) Ο(T(n)) και g(n) Ο(T(n)) ισχύει ότι α. f (n) + g(n) Ο(T(n)) β. f (n) g(n) Ο(T(n)) γ. f (n)/g(n) Ο() δ. ύο ή περισσότερα από τα πιο πάνω 5. Επιθυµούµε να υλοποιήσουµε τον ΑΤ ουρά µε διαδοχική χορήγηση µνήµης µέσω µιας εγγραφής που περιέχει ένα πίνακα Α[0..n-], στον οποίο αποθηκεύονται τα δεδοµένα κυκλικά µε τον γνωστό τρόπο, µια µεταβλητή Front που συγκρατεί τη θέση εξόδου της ουράς και µια µεταβλητή Βack που συγκρατεί τη θέση εισόδου της ουράς. Τότε: α. Οποιαδήποτε υλοποίηση της πράξης Dequeue απαιτεί χρόνο εκτέλεσης χείριστης περίπτωσης της τάξης Ο(m) όπου m ο αριθµός των στοιχείων της ουράς. β. Οποιαδήποτε υλοποίηση της πράξης Enqueue απαιτεί χρόνο εκτέλεσης χείριστης περίπτωσης της τάξης Ο(m) όπου m ο αριθµός των στοιχείων της ουράς. γ. Όλες οι πράξεις του ΑΤ µπορούν να υλοποιηθούν µέσω διαδικασιών χρονικής πολυπλοκότητας Ο(). δ. Η ζητούµενη υλοποίηση είναι αδύνατο να επιτευχθεί µέσω της συγκεκριµένης εγγραφής.
Άσκηση 2 (α) [2 µονάδες] Να αποφασίσετε κατά πόσο οι πιο κάτω προτάσεις αληθεύουν αποδεικνύοντας τις απαντήσεις σας. (i) n n O( 000n) Ας υποθέσουµε ότι n n O( 000n). Τότε υπάρχουν n 0, c, τέτοια ώστε n n 000cn για κάθε n n 0. Εποµένως, υπάρχουν n, c 0, τέτοια ώστε n 000c για κάθε n n 0. Εφόσον το n είναι µια συνάρτηση η οποία τείνει στο άπειρο καθώς το n τείνει στο άπειρο, είναι αδύνατη η ύπαρξη σταθεράς c που να το φράσσει. Αυτό µας οδηγεί σε αντίφαση και εποµένως η πρόταση αληθεύει. (ii) 2 n+ O(3 n ) Θέλουµε να εντοπίσουµε n 0, c, τέτοια ώστε 2 n+ c3 n για κάθε n n 0. Προφανώς, για c = 3 2 n+ 3 n+ για κάθε n και το ζητούµενο έπεται. (β) [8 µονάδες] Να αποδείξετε ότι ο χρόνος εκτέλεσης της πιο κάτω διαδικασίας είναι της τάξης Ο(n) επιλύνοντας οποιαδήποτε αναδροµική εξίσωση συναντήσετε. recursive(int n){ int sum=0; if (n>){ for ( int i = ; i <= n; i++ ) for ( int j = n-5; j < n; j++ ) sum++; recursive(n/4); Μπορείτε να χρησιµοποιήσετε το πιο κάτω άθροισµα. a n n+ i a = i= 0 a Ο χρόνος εκτέλεσης του εσωτερικού βρόχου είναι ίσος µε 5n O(n). Εποµένως ο χρόνος εκτέλεσης της αναδροµικής διαδικασίας δίνεται από την πιο κάτω αναδροµική εξίσωση: T() = T(n) = T(n/4) + n
Λύνουµε την εξίσωση µε τη µέθοδο της αντικατάστασης: T(n) = T(n/4) + n = T(n/6) + n/4 + n = = T(n/4 i ) + n/4 i- + + n Θέτουµε k = log 4 n (δηλαδή 4 k = n) και αντικαθιστούµε k για i παίρνοντας ότι: Τ(n) = T() + n/4 k- + + n = n (/4 k +... +/4 + /4 0 ) k / 4 / n 4( n ) = n = n = n / 4 3/ 4 3n = 4(n )/3 Άσκηση 3 (α) [3 µονάδες] Να περιγράψετε τη δοµή δεδοµένων κυκλική απλά συνδεδεµένη λίστα και να δώσετε τις εγγραφές (struct) που απαιτούνται για την υλοποίησή της. Μια κυκλική απλά συνδεδεµένη λίστα είναι µία λίστα κάθε κόµβος της οποίας είναι συνδεδεµένος µε τον επόµενο και ο τελευταίος κόµβος δείχνει στον πρώτο. Για την υλοποίησή της χρησιµοποιούνται οι πιο κάτω εγγραφές: typedef struct list { // οµή απλά συνδεδεµένης κυκλικής λίστας NODE *cycle; LIST; typedef struct node {// οµή κόµβου απλά συνδεδεµένης λίστας int data; struct node *next; NODE; (β) [6 µονάδες] Ορίζουµε τον αφηρηµένο τύπο δεδοµένων ουρά ως µία ακολουθία στοιχείων συνοδευόµενη από τις πράξεις: MakeEmpty() Enqueue(x, ) Dequeue() δηµιούργησε την κενή ουρά εισήγαγε το στοιχείο x στο τέλος της ουράς διέγραψε και επέστρεψε το στοιχείο που βρίσκεται στην κορυφή της ουράς Να εξηγήσετε µε σαφήνεια πως µπορούµε να υλοποιήσουµε αυτό τον ΑΤ χρησιµοποιώντας κυκλικές απλά συνδεδεµένες λίστες και να γράψετε την υλοποίηση των πράξεων σε ψευδοκώδικα. Οι πράξεις θα πρέπει να έχουν χρόνο εκτέλεσης Ο(). Μια ουρά µπορεί να υλοποιηθεί µε κυκλικές απλά συνδεδεµένες λίστες αν θεωρήσουµε ότι τα στοιχεία της ουράς είναι τοποθετηµένα µέσα στη λίστα έτσι ώστε κάθε στοιχείο να δείχνει σε εκείνο που εισήχθηκε ακριβώς πριν από αυτό και ο δείκτης cycle να δείχνει στο τελευταίο στοιχείο της ουράς. Με αυτό τον τρόπο έχουµε πρόσβαση και στα δύο άκρα της ουράς και µπορούµε να υλοποιήσουµε τις πράξεις ουράς σε χρόνο σταθερό όπως φαίνεται πιο κάτω.
Για παράδειγµα, ουρά µε τα στοιχεία, 3, 6, 8, όπου το είναι το πρώτο στοιχείο της ουράς και το 8 το τελευταίο αναπαρίσταται από την πιο κάτω κυκλική λίστα. 3 6 8 cycle MakeEmpty(LIST *){ ->cycle = NULL; Enqueue(int x, LIST *){ r = (NODE *) malloc (sizeof(node)); r->data = x; if ((->cycle == NULL) r->next = r; ->cycle = r; else r->next = ->cycle->next; ->cycle->next = r; ->cycle = r; int Dequeue(LIST *){ if ((->cycle == NULL) exit; p = ->cycle->next; x = p->data; if (p->next == p) ->cycle = NULL; else ->cycle->next = p->next; free(p); return x; (γ) [4 µονάδες] Να δείξετε σχηµατικά τις κυκλικές απλά συνδεδεµένες λίστες που λαµβάνονται, σύµφωνα µε την υλοποίησή σας, όταν κληθούν διαδοχικά οι πράξεις ΜakeEmpty(), Enqueue(,), Enqueue(7,), Dequeue(), Enqueue(5,). ΜakeEmpty() Enqueue(,) Enqueue(7,) 7 Dequeue() Enqueue(5,) 7 7 5
Άσκηση 4 [ µονάδες] Ξεκινώντας µε ένα άδειο 2-3 δένδρο, να εφαρµόσετε διαδοχικά εισαγωγή των στοιχείων 6, 4, 2, 9, 2, 7 και 8, δείχνοντας το αποτέλεσµα της κάθε µιας από τις εισαγωγές. κενό δένδρο 6 4 6 4 2 6 4 9 4 9 4 2 6 7 2 2 6 2 2 6 9 7 4 9 2 6 8 2 Άσκηση 5 (α) [8 µονάδες] Να αποδείξετε µε τη µέθοδο της µαθηµατικής επαγωγής ότι ένα AVL δένδρο ύψους h έχει το πολύ 2 h κόµβους που αποτελούν δεξί παιδί του πατέρα τους. Έστω Κ(h) ο µέγιστος αριθµός κόµβων ενός AVL δένδρου ύψους h που αποτελούν δεξί παιδί του πατέρα τους. Θα αποδείξουµε την πρόταση Π(h) K ( h) = 2 h για κάθε h Η απόδειξη µπορεί να γίνει µε µαθηµατική επαγωγή: Βασική περίπτωση: h = H περίπτωση αυτή αφορά τα τρία διαφορετικά AVL δένδρα µε ύψος. Ο µέγιστος αριθµός κόµβων που αποτελούν δεξί παιδί του πατέρα τους ανάµεσα σε αυτά τα δένδρα είναι = 2 -, Υπόθεση της επαγωγής: Έστω Π(k) για κάθε k< m για κάποιο m. Βήµα της επαγωγής: Θα δείξουµε ότι Π(m). Έστω ένα AVL δένδρο ύψους m το οποίο παρουσιάζει τον µέγιστο αριθµό κόµβων που αποτελούν δεξί παιδί του πατέρα τους. Το δένδρο αυτό αποτελείται από µια
ρίζα και δύο υπόδενδρα που ριζώνουν σ αυτή των οποίων το ύψος, εφόσον θέλουµε να µεγιστοποιήσουµε το Κ(h), είναι το µέγιστο δυνατόν και ίσο µε m. Οι κόµβοι που αποτελούν δεξί παιδί του πατέρα τους σε αυτό το δένδρο είναι οι κόµβοι που αποτελούν δεξί παιδί του πατέρα τους στο αριστερό υπόδενδρο (ίσοι µε Κ(m-)) οι κόµβοι που αποτελούν δεξί παιδί του πατέρα τους στο δεξί υπόδενδρο (επίσης ίσοι µε Κ(m-)) και το δεξί παιδί της ρίζας (η ρίζα του δεξιού υποδένδρου). Εποµένως Κ(m) = 2 Κ(m-) + και από την υπόθεση της επαγωγής Κ(m) = 2(2 m- -) + = 2 m Το ζητούµενο έπεται. (β) [8 µονάδες] Να γράψετε αναδροµική διαδικασία η οποία, µε δεδοµένο εισόδου δείκτη στη ρίζα ενός AVL-δένδρου να υπολογίζει και να επιστρέφει το γινόµενο των κλειδιών όλων των κόµβων του δένδρου που αποτελούν δεξί παιδί του πατέρα τους. Για παράδειγµα, στο πιο κάτω δένδρο θα πρέπει να επιστραφεί η τιµή 90. 6 4 9 2 7 0 (Να αναφέρετε τις δοµές που θα χρησιµοποιήσετε για υλοποίηση του AVL-δένδρου.) Για την υλοποίηση ενός AVL δένδρου χρησιµοποιούµε την εγγραφή: struct AVLNode{ int height; int key; struct AVLNode *left; struct AVLNode *right; H ζητούµενη αναδροµική διαδικασία λειτουργεί βάσει της εξής ιδέας: εδοµένου ότι το δένδρο δεν είναι κενό, - Υπολογίζουµε αναδροµικά το ζητούµενο γινόµενο στο αριστερό υπόδενδρο (αν υπάρχει) - και πολλαπλασιάζουµε το αποτέλεσµα µε το κλειδί του δεξιού παιδιού επί το ζητούµενο γινόµενο στο υπόδενδρο που ριζώνει σε αυτό, αν υπάρχει. int RecProductRC(AVLNode *p){ x=; if (p!= NULL) if (p->left!= NULL) x = RecProductRC(p->left); if (p->right!= NULL) x = x*recproductrc(p->right); x = x*(p->right)->data return x;
(γ) [0 µονάδες] Να γράψετε µη-αναδροµική διαδικασία η οποία µε δεδοµένο εισόδου δείκτη στη ρίζα ενός AVL-δένδρου να υπολογίζει και να επιστρέφει το γινόµενο των κλειδιών όλων των κόµβων του δένδρου που αποτελούν δεξί παιδί του πατέρα τους. Yποθέτουµε την ύπαρξη υλοποίησης στοίβας, και συγκεκριµένα των πράξεων ουράς MakeEmpty(), Push(x,S), και Pop(S). H µη-αναδροµική διαδικασία έχει ως εξής:. Θέτουµε prod ίσο µε και αρχικοποιούµε µια κενή στοίβα. 2. Εφόσον ο δείκτης δεν είναι NULL και η στοίβα δεν είναι κενή προχωρούµε στο βήµα 3, διαφορετικά επιστρέφουµε την τιµή του prod και τερµατίζουµε τη διαδικασία. 3. Αν ο δείκτης δεν είναι NULL, τοποθετούµε στη στοίβα τον δείκτη p->right εφόσον και αυτός δεν είναι NULL και προχωρούµε στο p->left. Επαναλαµβάνουµε από το βήµα 2. 4. Αν ο δείκτης είναι NULL ανασύρουµε τον κόµβο κορυφής της στοίβας. Πολλαπλασιάζουµε το prod µε το στοιχείο του κόµβου και συνεχίζουµε µε επεξεργασία του δείκτη από το βήµα 2 int NRProductRC(AVLNode *p){ prod = ; MakeEmpty(S); while(p OR!IsEmpty(S)){ if (p) if (p->right!= NULL) Push(p->right,S); p = p->left; else p = Pop(S); prod = prod p->val; return prod;