Κατ οίκον Εργασία 2 Σκελετοί Λύσεων Άσκηση 1 Ο ζητούμενος ΑΤΔ μπορεί να υλοποιηθεί ως μια ακολουθία από στοιχεία τύπου window συνοδευόμενη από τις πράξεις: MakeNewWindow(L,w) Destroy(L,w) SwitchTo(L,w) η οποία δημιουργεί και τοποθετεί τo παράθυρο w στην κορυφή της ακολουθίας L η οποία διαγράφει το παράθυρο w από την L η οποία τοποθετεί το παράθυρο w στην κορυφή της L Και στις δύο υλοποιήσεις που ακολουθούν, υποθέτουμε την ύπαρξη βοηθητικών διαδικασιών που, για απλότητα, συμβολίζουμε ως ==,!= και =, οι οποίες αποφασίζουν κατά πόσο δύο παράθυρα είναι ίσα (w == z), άνισα (w!= z) και αναθέτουν ένα παράθυρο σε μια μεταβλητή τύπου window (x = w). Διαδοχική χορήγηση μνήμης Υποθέτουμε ότι στο ζητούμενο περιβάλλον μπορούν να συνυπάρχουν ταυτόχρονα μέχρι max παράθυρα και ορίζουμε τον τύπο WList για υλοποίηση της λίστας παραθύρων. Θεωρούμε ότι το πεδίο size συγκρατεί τον αριθμό παραθύρων που περιέχει η λίστα και επομένως αποτελεί και τη θέση εισαγωγής καινούριου παραθύρου. typedef struct Wlist{ window elements[max]; wlist; Οι διαδικασίες υλοποιούνται ως εξής: void MakeNewWindow(wlist *L, window w){ if (L->size < max){ L->elements[L->size] = w; L->size++; Χρόνος Εκτέλεσης: O(1) void Delete(wlist *L, window w) { i=0; while (i< L->size AND L->elements[i]!= w) i++; if (i!= L->size) { for (j=i; j < L->size; j++) L->elements[j]= L->elements[j+1] L->size--; 1
void SwitchTo(wlist *L, window w){ i=0; while (i < L->size AND L->elements[i]!= w) i++; if (i!= L->size){ for (j=1; j < L->size -1; j++) L->elements[j] = L->elements[j+1]; L->elements[L->size-1] = w; Συνδετική χορήγηση μνήμης Θεωρούμε ότι το παράθυρο που είναι στην κορυφή της οθόνης βρίσκεται στην πρώτη θέση της λίστας. typedef struct Window{ win window; struct Node *next; window; typedef struct WList{ window *top; wlist; Οι διαδικασίες υλοποιούνται ως εξής: MakeNewWindow(wlist *L, window w){ p = malloc(sizeof (window)); p->win = w; p->next = L->top; L->top = p; L->size++; Χρόνος Εκτέλεσης: O(1) Delete(wlist *L, window w) { window *t, *p = L->top; if (p == null) exit if (p->win == w) L->top = p->next; free(p); exit; while (p->next!= null AND (p->next)->win!= w) p = p->next; if (p->next!= null) t = p->next; 2
p->next = p->next->next; L->size--; free(t); SwitchTo(wlist *L, window w){ if (L->top == null OR (L->top)->win == w) exit; while (p->next!= null AND (p->next)->win!= w) { p = p->next; if (p->next!= null) t = p->next; p->next = p->next->next; t->next = L->top; L->top = t; Άσκηση 2 Έστω M= [x 0, x 1,, x n-1 ] η σειρά με την οποία θέλουμε να διατάξουμε τα τραίνα. Yποθέτουμε την ύπαρξη υλοποίησης στοίβας και συγκεκριμένα των πράξεων, MakeEmpty, IsEmpty, Push, Top και Pop. Υλοποιούμε την κάθετη γραμμή ως στοίβα και εργαζόμαστε ως εξής: Metathesi(int M[]) { i=1; // ο αριθμός του πρώτου τραίνου στη δεξιά γραμμή j=0; MakeEmpty(S); while (i<=n){ if (i == Μ[j]) { i++; j++; if (i < Μ[j]) { { Push(i,S); i++ // δείκτης στον πίνακα Μ, όπου M[j] είναι το επόμενο τραίνο το οποίο // πρέπει να περάσει προς τα αριστερά. if (Top(S) == Μ[j]) { Pop(S) j++; // αν το επόμενο τραίνο της μετάθεσης είναι το τραίνο που // βρίσκεται στη δεξιά γραμμή // μετακίνησε το τραίνο i προς τα αριστερά // και προχώρησε στο επόμενο τρένο της διάταξης Μ // αν το επόμενο τραίνο της μετάθεσης έχει μεγαλύτερο // αριθμό από το τραίνο που βρίσκεται στη δεξιά γραμμή // προχώρησε να το εντοπίσεις κατεβάζονται στην κάτω //γραμμή (στοίβα) το τραίνο i // και έλεγξε το επόμενο τραίνο στη δεξιά γραμμή // διαφορετικά το τραίνο που ψάχνεις βρίσκεται μέσα στη στοίβα // αν βρίσκεται στην κορυφή της στοίβας // το μετακινούμε στα αριστερά // και προχωρούμε στο επόμενο στοιχείο της Μ 3
report error // διαφορετικά η μετάθεση είναι ανέφικτη και // δίνουμε μήνυμα λάθους if (IsEmpty(S)) // Εάν δεν υπάρχουν στοιχεία στη στοίβα σημαίνει ότι έχουμε report success; // μετακινήσει όλα τα στοιχεία με επιτυχία στα αριστερά report error; Άσκηση 3 (1) Ευθύγραμμη διπλά συνδεδεμένη λίστα. Χρησιμοποιούμε τις πιο κάτω δομές typedef struct DNode{ type data; struct node *next; struct node *prev; dnode; typedef struct DList{ node *top; dlist; Μη-αναδρομική εκδοχή void InvertList(dlist *L){ node *p1, *p2, *p3; p1 = L->top; if (p1 == NULL) return; p2 = p1 -> next; p1->prev = p2; p1->next = NULL; while (p2!= NULL) { p3 = p2->next; p2->prev = p3; p1 = p2; p2 = p3; L->top = p1; Aναδρομική εκδοχή Σημειώστε ότι το z που επιστρέφεται από την αναδρομική κλήση RecInvert είναι δείκτης προς τον τελευταίο κόμβο της αρχικής λίστας το οποίο πρέπει να γίνει ο πρώτος κόμβος της καινούριας. void RecInvertList(list *L) { if (L->top!= NULL) L->top = RecInvert(L->top) 4
RecInvert(node *p){ node *z; if (p->next!= NULL) { z = RecInvert(p->next); q = p->next; p->next = p ->prev; p->prev = q; return z; p->next = p->prev; p->prev = NULL; return p; Προφανώς και οι δύο αλγόριθμοι είναι της τάξης Ο(n), όπου n είναι ο αριθμός κόμβων της λίστας. Εντούτοις, ο αναδρομικός αλγόριθμος είναι λιγότερο αποδοτικός από άποψη χώρου. Αυτό οφείλεται στο ότι για κάθε αναδρομική κλήση της διαδικασίας ένα σύνολο από λέξεις (οι παράμετροι της συνάρτησης, η διεύθυνση επιστροφής και οι τοπικές μεταβλητές της καλούσας συνάρτησης) φυλάσσονται μέσα στη στοίβα του προγράμματος από όπου μπορούν να ανασυρθούν. Κατά συνέπεια, κατά την επεξεργασία του τελευταίου κόμβου της λίστας θα υπάρχουν σε κατάσταση αναμονής n 1 κλήσεις (όπου n είναι το μέγεθος της λίστας) που αφορούν όλους τους προηγούμενους κόμβους. Σημειώνουμε ότι η δημιουργία και επεξεργασία της στοίβας αυτής απαιτεί και επιπλέον χρόνο εκτός από χώρο έχοντας σαν αποτέλεσμα, στην πράξη, η αναδρομική διαδικασία να είναι λιγότερο αποδοτική. (2) Ευθύγραμμη απλά συνδεδεμένη λίστα. Χρησιμοποιούμε τις πιο κάτω δομές. typedef struct Node{ typedef struct List{ type data; node *top; struct node *next; node; list; Μη-αναδρομική εκδοχή void InvertList(list *L){ node *p1, *p2, *p3; p1 = L->top; if (p1 == NULL) return; p2 = p1 -> next; p1->next = NULL; while (p2!= NULL) { p3 = p2->next; p1 = p2; p2 = p3; L->top = p1; 5
Aναδρομική εκδοχή void RecInvertList(list *L) { if (L->top!= NULL) L->top = RecInvert(L->top) RecInvert(node *p){ node *z; if (p->next!= NULL) { z = RecInvert(p->next); (p ->next)->next = p; p->next = NULL; return Z; return p; Ισχύουν τα ίδια σχόλια με το (1) (3) Κυκλική απλά συνδεδεμένη λίστα με κεφαλή. Χρησιμοποιούμε τις πιο κάτω δομές: typedef struct Node{ typedef struct List{ type data; node *head; struct node *next; node; list; Μη-αναδρομική εκδοχή void InvertList(list *L){ node *p1, *p2, *p3; p1 = L->head; p2 = (L->head)->next; while (p2!= L->head) { p3 = p2->next; p1 = p2; p2 = p3; Aναδρομική εκδοχή void RecInvertList(list *L) { (L->head)->next->next = L->head; RecInvert(L->head->next, L->head); 6
RecInvert(node *p, node *q){ node *z; if (p!= q) { p ->next)->next = p; RecInvert(p->next,q); 7