ΕΘΝΙΚΟ ΜΕΤΣΟΒΙΟ ΠΟΛΥΤΕΧΝΕΙΟ ΤΜΗΜΑ ΗΛΕΚΤΡΟΛΟΓΩΝ ΜΗΧΑΝΙΚΩΝ ΚΑΙ ΜΗΧΑΝΙΚΩΝ ΥΠΟΛΟΓΙΣΤΩΝ ΤΟΜΕΑΣ ΠΛΗΡΟΦΟΡΙΚΗΣ ΙΟΥΝΙΟΣ 2000 ΔΟΜΕΣ ΔΕΔΟΜΕΝΩΝ ΚΑΝΟΝΙΚΗ ΕΞΕΤΑΣΗ (Ενδεικτικές Λύσεις) Τ. Σελλής - N. Koζύρης Θέμα 1ο (20%): Έστω ένας πίνακας Α διαστάσεων 8x8. Αν θελήσουμε να αποθηκεύσουμε τα στοιχεία του πίνακα A={a(i,j) σε μονοδιάστατο πίνακα Β, η αντιστοίχηση των στοιχείων του Α στα στοιχεία του Β γίνεται όπως ξέρουμε με κάποιο τρόπο διάσχισης του πίνακα Α (π.χ. κατά στήλη, κατά γραμμή, κλπ). α) Έστω η τοποθέτηση των στοιχείων a(i,j) στον πίνακα Β με βάση τη διάσχιση που φαίνεται στο παρακάτω σχήμα, αρχίζοντας από το a(8,1). Να γραφεί η συνάρτηση loc(a(i,j)) που τοποθετεί τα στοιχεία a(i,j) στον πίνακα B[1..64]. Μπορείτε να γράψετε πρόγραμμα σε C ή Pascal ή ψευδοκώδικα. β) Γενικεύοντας το (α), να γραφεί η συνάρτηση loc(a(i,j),n) που τοποθετεί τα στοιχεία a(i,j) στον πίνακα B, αν ο πίνακας Α είναι διαστάσεων 2 n x2 n (Μπορείτε να γράψετε πρόγραμμα σε C ή Pascal ή ψευδοκώδικα. Παρατηρείστε επίσης την αναδρομικότητα της σειράς διάσχισης των στοιχείων) 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 Λύση Θέμα 1ο: (α) Παρατηρούμε ότι, ο αρχικός πίνακας 8x8 έχει χωριστεί αρχικά σε 4 υπο-περιοχές, οι οποίες φαίνονται στο παρακάτω σχήμα: [1..4] [5..8] [1..4] [5..8] 1
Η κάθε μια από αυτές τις περιοχές διαιρείται σε 4 νέες υπο-περιοχές με όμοιο τρόπο. Τέλος, η διαδικασία αυτή επαναλαμβάνεται για τρίτη φορά σε κάθε μία από τις υπο-περιοχές, έτσι όπως δείχνει το σχήμα. Αν ονομάσουμε τα 3 επίπεδα περιοχών Ι, ΙΙ και ΙΙΙ αντίστοιχα, τότε για κάθε στοιχείο a[i, j] του πίνακα Α αρκεί να βρούμε σε πια περιοχή του επιπέδου I βρίσκεται, σε πια του ΙΙ και σε πια του ΙΙΙ. Τότε, αν υπολογίσουμε τις περιοχές που προηγούνται (offset), θα βρούμε την θέση του a στον μονοδιάστατο πίνακα Β. Σημειώνουμε ότι κάθε υπο-περιοχή του επιπέδου Ι αποτελείται από 16 θέσεις, κάθε υποπεριοχή του επιπέδου ΙΙ αποτελείται από 4 θέσεις και κάθε υπο-περιοχή του επιπέδου Ι αποτελείται από 1 θέση. Παρατηρούμε ότι, οι 4 υπο-περιοχές του ίδιου επιπέδου μπαίνουν σε μια σειρά βάσει της διάσχισης που κάνουμε και η οποία σειρά δίνεται από την συνάρτηση: int order(int i,int j) { if(i==2 && j==1) return 1; if(i==1 && j==1) return 2; if(i==2 && j==2) return 3; if(i==1 && j==2) return 4; Για την συνάρτηση order θεωρήσαμε 1<=i<=2 και 1<=j<=2, δηλαδή ότι οι υποπεριοχές του ίδιου επιπέδου έχουν «συντεταγμένες» όπως φαίνεται στο παρακάτω σχήμα: 1 2 1 2 Παρακάτω δίνεται η συνάρτηση loc()η οποία δέχεται ως είσοδο τις συντεταγμένες του στοιχείου a[i,j] του πίνακα Α, όπου 1<=i<=8 και 1<=j<=8 και επιστρέφει την θέση του a στον πίνακα Β. int loc(int i,int j){ int offset; /* επίπεδο Ι */ if(i>4 && j<=4) offset = 0; /* == (order(2,1)-1)*16 */ if(i<=4 && j<=4) offset = (order(1,1)-1)*16; if(i>4 && j>4)) offset = (order(2,2)-1)*16; if(i<=4 && j>4) offset = (order(1,2)-1)*16; 2
/* επίπεδο ΙI */ if(i>4) i = i 4; /*μετατροπή στο διάστημα [1..4]*/ if(j>4) j = j 4; if(i>2 && j<=2) offset = 0; /* == 0 +(order(2,1)-1)*4 */ if(i<=2 && j<=2) offset = offset + (order(1,1)-1)*4; if(i>2 && j>2) offset = offset + (order(2,2)-1)*4; if(i<=2 && j>2) offset = offset + (order(1,2)-1)*4; /* επίπεδο ΙII */ if(i>2) i = i 2; /*μετατροπή στο διάστημα [1..2]*/ if(j>2) j = j 2; offset = offset + order(i,j); return offset; Το πρόγραμμα που γεμίζει τον πίνακα Β από τον Α θα είναι επομένως: for(i=1; i<=8; i++) for(j=1; j<=8; j++) B[loc(i,j)] = a[i][j]; (β) Θα παρουσιάσουμε μια αναδρομική συνάρτηση loc(), η οποία παίρνει σαν είσοδο τις συντεταγμένες του στοιχείου a[i,j] του πίνακα Α διαστάσεων 2 n x2 n και το n και επιστρέφει την θέση (offset) του a στον μονοδιάστατο πίνακα Β. int loc(int i, int j, int n) { int offset; int N = 2 n ; if(n==1) { /* τερματισμός αναδρομής */ offset = order(i,j); return offset; if(i>n/2 && j<=n/2) offset = 0; /* == (order(2,1)-1)*(n/2)*(n/2) */ if(i<=n/2 && j<=n/2) offset = (order(1,1)-1)*(n/2)*(n/2); if(i>n/2 && j>n/2)) offset = (order(2,2)-1)* (N/2)*(N/2); if(i<=n/2 && j>n/2) offset = (order(1,2)-1)* (N/2)*(N/2); if(i>n/2) i = i N/2; if(j>n/2) j = j N/2; offset = offset + loc(i,j,n-1); return offset; 3
Το πρόγραμμα που γεμίζει τον πίνακα Β από τον Α θα είναι επομένως: for(i=1; i<=2 n ; i++) for(j=1; j<=2 n ; j++) B[loc(i,j,n)] = a[i][j]; Αν θα θέλαμε να απαντήσουμε στο ίδιο ερώτημα δίνοντας την συνάρτηση loc σε κλειστή μορφή, τότε αυτή θα ήταν η εξής: loc(i, j,n) n 2 i j 1 = + 2 x 2 n 1 n 1 2 2 i 1 + loc + i mod 2 j 1 ( ) ( ) 2 + 1, + j mod 2 + 1,n x 2 1 2 με 1, 2, loc(i, j,1) = 3, 4, j = 1,i = 2 j = 1,i = 1 j = 2,i = 2 j = 2,i = 1 δηλ. για τον πίνακα μεγέθους 2 n x 2 n και για την θέση a[i, j], αθροίζουμε τις περιοχές μεγέθους 2 n-1 x 2 n-1 που προηγούνται και καλούμε αναδρομικά την loc για την περιοχή μεγέθους 2 n-1 x 2 n-1 μέσα την οποία βρίσκεται το a. Η αναδρομή τερματίζει όταν φτάσουμε στην περιοχή μεγέθους 2x2 που βρίσκεται το α. Θέμα 2ο (20%): α) Να γραφεί συνάρτηση η οποία να υπολογίζει το βάθος ενός δυαδικού δέντρου (μέγιστη απόσταση φύλλου από τη ρίζα). β) Το αριστερό (δεξιό) βάθος ενός δυαδικού δέντρου ορίζεται σαν ο μέγιστος αριθμός αριστερών (δεξιών) συνδέσμων που διασχίζει κανείς για να πάει από τη ρίζα στα φύλλα. Να γραφεί συνάρτηση η οποία να υπολογίζει το αριστερό βάθος ενός δυαδικού δέντρου. Σημείωση: οι συναρτήσεις μπορούν να γραφούν σε C ή Pascal ή ψευδοκώδικα. Λύση Θέμα 2ο: Θεωρώντας το δένδρο σαν μια δομή struct node { int data *node left *node right (α) Ο αλγόριθμος είναι int depth (node* root) { if (root == NULL) return 0; return (1 + max(depth(node->left, depth(node->right)); 4
(β) Ο αλγόριθμος είναι int ldepth (node* root) { if (root == NULL) return 0; if (root->left) return (max(1+ldepth(node->left, ldepth(node->right)) else return (ldepth(root->right); Θέμα 3ο (20%): Ένα φορτηγό διανομής γαλακτοκομικών προϊόντων ξεκινά κάθε φορά από τη κεντρική αποθήκη των Αθηνών με σκοπό να παραδώσει το φορτίο του σε μια από τις κεντρικές αποθήκες διανομής των πόλεων της Κεντρικής Ελλάδας και Ηπείρου που απεικονίζονται στον παρακάτω χάρτη. Θεωρούμε επίσης δεδομένο ένα διδιάστατο πίνακα Ιωάννινα Dist(πόλη1,πόλη2) όπου δίνονται οι Τρίκαλα Λάρισα χιλιομετρικές αποστάσεις μεταξύ των πόλεων. Στον πίνακα αυτό, όπου δεν δίνεται κάποια απόσταση μεταξύ δύο πόλεων κενό στοιχείο -, υποθέτουμε ότι δεν υπάρχει απευθείας δρόμος μεταξύ των δύο αυτών πόλεων. Σκοπός του φορτηγού είναι να φτάνει στο προορισμό του διανύοντας τον ελάχιστο αριθμό Κm. α) Να γραφεί αλγόριθμος που να μας δίνει τον ελάχιστο αριθμό Κm και την ακολουθία Καρδίτσα Λαμία Βόλος των ενδιάμεσων πόλεων που επισκέπτεται το φορτηγό μέχρι τον τελικό προορισμό του κάθε φορά. β) Αν θέλαμε να αποθηκεύσουμε σε μια δομή όλες τις ενδιάμεσες πόλεις που επισκέπτεται το φορτηγό για όλα τα δρομολόγια που κάνει (δηλ. για όλους τους δυνατούς προορισμούς), ποια είναι η ελάχιστη δομή που μπορεί να χρησιμοποιηθεί και γιατί; Λύση Θέμα 3ο: Έστω το σύνολο των πόλεων TOWNS={Αθήνα, Λαμία, Τρίκαλα, Καρδίτσα, Βόλος, Λάρισα, Ιωάννινα, n:= TOWNS=7. Θεωρούμε ότι στον πίνακα αποστάσεων γειτονικών πόλεων dist, όταν dist(town 1,town 2 )=, δεν υπάρχει απευθείας δρόμος μεταξύ των πόλεων αυτών. Αθήνα minimum_distance (s, TOWNS) begin S = ; S = TOWNS; path_length (s, i) = ; /*αρχικά το μήκος από την s προς οποιοδήποτε i είναι */ path_length (s, s) = 0; pred (s) = 0; 5
while S <n do begin έστω i S ένας κόμβος για τον οποίο: path_length (s,i) = min{ path_length (s,j): j S S = S {i; S = S - {i; for each j: dist (i, j) < do if path_length (s, j) > path_length (s, i) + dist (i, j) then begin path_length (s, j) = path_length (s, i) + dist (i, j); pred(j) = i; end end end return path_length, pred; Ο πίνακας path_length(αθηνα, i), i TOWNS, περιέχει το μήκος όλων των συντομότερων μονοπατιών προς όλους τους δυνατούς προορισμούς. H συνάρτηση minimum_distance καλέιται με παραμέτρους την s=αθηνα και το σύνολο TOWNS. Έστω ότι ο συντομότερος δρόμος από την αφετερία source προς τον προορισμό dest είναι από το μονοπάτι: source - town 1 - town 2 - - town l - town k - - dest, τότε ο τελεστής pred(town k ) = town l δίνει την αμέσως προηγούμενη πόλη town l από την town k πάνω στο συντομότερο μονοπάτι από την πόλη source προς τη dest. Σύμφωνα με τον παραπάνω αλγόριθμο του Dijkstra, το συντομότερο μονοπάτι από την source προς οποιοδήποτε προορισμό είναι και συντομότερο μονοπάτι προς κάθε ενδιάμεση πόλη. (Άλλωστε, αν υπήρχε ένα άλλο πιο σύντομο μονοπάτι από την source προς την ενδιάμεση πόλη, τότε το συνολικό μονοπάτι προς το προορισμό, μέσω του νέου δρόμου, θα ήταν μικρότερο, άρα άτοπο). Ο τελεστής pred κρατά για κάθε πόλη την προηγούμενή της, η οποία είναι μοναδική. Αυτό μας θυμίζει την ιδιότητα κόμβου δέντρου που έχει ένα πατέρα. Αυτό σε συνδυασμό με την παραπάνω ιδιότητα μας λέει ότι η δομή στην οποία αποθηκεύονται όλα τα μονοπάτια από την source προς όλους τους δυνατούς προορισμούς, είναι ένα δέντρο με συνολικά n κόμβους, όπου n το πλήθος των προορισμών. Το δέντρο αυτό λέγεται δέντρο ελάχιστων μονοπατιών και είναι ένα επικαλυπτικό (spanning) δέντρο για τον γράφο των πόλεων. Θέμα 4ο (25%): Να γραφεί διαδικασία σε C ή Pascal ή ψευδοκώδικα η οποία να ταξινομεί συνδεδεμένη γραμμική λίστα με κατάλληλη προσαρμογή του αλγορίθμου Bubblesort (H μετακίνηση των φυσαλίδων μπορεί να γίνεται από αριστερά προς τα δεξιά. Ένας δείκτης μπορεί να καθορίζει κάθε φορά το τέλος του τμήματος της λίστας, στο οποίο γίνεται η σάρωση). Λύση Θέμα 4ο: Παρακάτω παρουσιάζεται μια ρουτίνα ταξινόμησης, η οποία εφαρμόζει τον αλγόριθμο Bubblesort σε συνδεδεμένες λίστες. Σημειώστε ότι η ανταλλαγή (swapping) γίνεται ανταλλάσσοντας δείκτες. Ακολουθεί ο ορισμός κόμβου συνδεδεμένης λίστας: typedef struct node *nodeptr; typedef struct node { int data; nodeptr next; nodestruct; 6
Ξαναγράφουμε τα φωλιασμένα for-loops της Bubblesort ώστε να είναι πιο εύκολο να τροποποιηθούν για τη χρήση συνδεδεμένων λιστών. for(i = n-1; i>=0; i--) for(j=0; j<=i; j++) if (table[j] > table[j+1]) Swap(table[j], table[j+1]); Παρατηρήστε ότι ο εξωτερικός βρόχος μειώνεται ενώ ο εσωτερικός αυξάνει. Head--> ----> ----> ----> ----> ----> ---->NULL - - - - - - - - - - - - Βασιζόμενοι στην πιο πάνω μορφή και αντικαθιστώντας τα for loops με while loops, ο αλγόριθμος έχει ως εξής: void BubbleSortList(nodeptr head) { nodeptr tail, btail, j, bj, temp; /* Prefix "b" stands for "before". Thus bj points to the node "before" j, i.e. bj->next==j */ /* We find the tail and btail nodes first */ tail=head; btail=null; while(tail->next) { btail=tail; tail=tail->next; /* Each time, btail will be computed by the inner loop, which runs the list forward. */ while (btail!= head) { j = head; bj=null; /* initialization of the condition */ while (j!= btail->next) { if (j->data > j->next->data) Swap(j, j->next, bj); temp=bj; bj=bj->next; j = bj->next; /* Although it isn't needed. Check swap! */ /* By the end of the inner while, temp points to the node before bj. Moreover, bj == btail and j will be j==btail->next. Thus, temp is the next value for btail (btail runs backwards, approaching head in each step */ btail=temp; /* End of BubbleSortList */ /* the following function swaps indices (links). Note that we don't alter the nodes pointed to by j and j->next. */ void Swap(nodeptr x, nodeptr y, nodeptr bx) { x->next = y->next; if (bx) bx->next = y; /* bx might be NULL */ y->next = x; /* End of swap */ 7
Θέμα 5ο (15%): Να σχεδιαστεί βήμα-βήμα το δυαδικό δέντρο που προκύπτει από την εισαγωγή των εξής στοιχείων: 11 20 16 7 25 2 12 13 9 21 3, έτσι ώστε σε κάθε βήμα το δέντρο που προκύπτει να είναι AVL. Λύση Θέμα 5ο: 11 : (11) 20 : (11(-,20)) 16 : (11(-,20(16,-))) --RL(11)--> (16(11,20)) 7 : (16(11(7,-),20)) 25 : (16(11(7,-),20(-,25))) 2:(16(11(7(2,-),-),20(-,25))) LL(11)--> (16(7(2,11),20(-25))) 12 : (16(7(2,11(-,12)),20(-,25))) 13 : (16(7(2,11(-,12(-,13))),20(-,25))) --RR(11)--> (16(7(2,12(11,13)),20(-,25))) 9 : (16(7(2,12(11(9,-),13)),20(-,25))) --RL(7)--> (16(11(7(2,9),12(-,13)),20(-,25))) 21 : (16(11(7(2,9),12(-,13)),20(-,25(21,-)))) --RL(20)--> (16(11(7(2,9),12(-,13)),21(20,25))) 3 : (16(11(7(2(-,3),9),12(-,13)),21(20,25))) --LL(16)--> (11(7(2(-,3),9),16(12(-,13),21(20,25)))) Τελικό AVL: 11 7 16 2 9 12 21 3 13 20 25 8