υναµική έσµευση Μνήµης (συν.) Στην ενότητα αυτή θα µελετηθούν: Μια εφαρµογή συνδεδεµένων λιστών ιπλά συνδεδεµένες Λίστες ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 1 Εφαρµογή Ζητούµενο: Πρόγραµµα που παίρνει σαν όρισµα το όνοµα ενός αρχείου και µετρά τον αριθµό των εµφανίσεων όλων των διαφορετικών λέξεων που περιέχονται στο αρχείο. Αφού το σύνολο των πιθανών λέξεων του αρχείου δεν είναι εκ των προτέρων γνωστό δεν µπορούµε να τις φυλάξουµε σε ταξινοµηµένο πίνακα στο οποίο θα εφαρµόσουµε δυαδική διερεύνηση (δες ιάλεξη 9 όπου µετρούσαµε τον αριθµό των εµφανίσεων κάθε µιας από τις δεσµευµένες λέξεις της C). Μπορούµε όµως να διατηρούµε σε κάποια δυναµική δοµή τις διαφορετικές λέξεις του αρχείου που έχουµε συναντήσει µέχρι στιγµής µαζί µε ένα µετρητή για τον (µέχρι στιγµής) αριθµό των εµφανίσεων της λέξης. Ποια θα ήταν µια κατάλληλη δυναµική δοµή; Μια συνδεδεµένη λίστα... ή... ; ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 2 1
οµή λύσης: Λύση 1 : Συνδεδεµένη Λίστα άνοιξε το αρχείο; δηµιούργησε µια κενή λίστα; while (δεν έχεις φτάσει στο τέλος του αρχείου) if (η επόµενη λέξη υπάρχει ήδη στη λίστα) αύξησε την τιµή του µετρητή που αντιστοιχεί στη λέξη κατά ένα; else πρόσθεσε κόµβο µε τη λέξη στη λίστα; τύπωσε τα στοιχεία της λίστας; κλείσε το αρχείο; ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 3 Λύση 1 : Συνδεδεµένη Λίστα Οι κόµβοι της λίστας θα περιέχουν τρία πεδία: typedef struct node{ char word[maxword]; int count; struct node next; NODE; O κόµβος που ορίζει τη λίστα θα έχει τη µορφή: typedef struct list { NODE *top; LIST; ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 4 2
Λύση 1 : Συνδεδεµένη Λίστα Θα χρειαστούµε τις εξής χρήσιµες συναρτήσεις: void *addlist(list *, char *) : Η συνάρτηση αυτή παίρνει σαν παράµετρους δείκτη προς τη λίστα και δείκτη προς µια λέξη και ψάχνει να βρεί τη λέξη στη λίστα. Αν υπάρχει, τότε αυξάνει το πεδίο count του κοµβου κατά ένα, διαφορετικά δηµιουργεί νέο κόµβο που αντιστοιχεί στη νέα λέξη. void printlist(list *) : Η συνάρτηση τυπώνει τις λέξεις της λίστας που δείχνεται από τον δείκτη που της δίνεται ως παράµετρος µαζί µε τον αριθµό των εµφανίσεων κάθε µιας από αυτές. ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 5 #include <stdio.h> #include <string.h> #define MAXWORD 100 H συνάρτηση main void addlist(list *, char *); void printlist(list *); int main(int argc, char *argv){ LIST *list; char word[maxword]; FILE *inp; if (argc!= 2) { printf("error in the use of the program\n"); exit(1); ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 6 3
list.top = NULL; H συνάρτηση main inp = fopen(argv[argc-1], "r"); while(fscanf(inp, %s, word)!= EOF) if (isalpha(word[0]) addlist(&list, word); printlist(&list); fclose(inp); return 0; ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 7 H συνάρτηση printlist void printlist(list *l){ NODE *p = l->top; while (p!= NULL){ printf( Word %s appears %d times\n, p->word, p->count); p = p->next; ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 8 4
H συνάρτηση addlist void addlist(list *l, char *w){ l->top = addnode(l->top, w); ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 9 H συνάρτηση addnode NODE *addnode(node *p, char *w){ if (p == NULL){ p = (NODE *) malloc (sizeof(node)); strcpy(p -> word, w); p->count = 1; p->next = NULL; else if (strcmp(p -> word, w ) == 0) p -> count ++; else p -> next = addnode(p -> next, w); return p; ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 10 5
Λύση 1 : Συνδεδεµένη Λίστα (συν.) Παρατηρούµε πως ένα σηµείο πιθανής σπατάλης χώρου είναι ότι σε κάθε κόµβο δεσµεύουµε µνήµη για λέξεις 100 χαρακτήρων. Μπορούµε να αποφύγουµε αυτή τη σπατάλη δεσµεύοντας κάθε φορά ακριβώς όση µνήµη χρειαζόµαστε κάνοντας τις εξής αλλαγές στον κώδικά µας: Ορισµός κόµβου λίστας: typedef struct node{ char *word; int count; struct node next; NODE; ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 11 εύτερη εκδοχή της addnode NODE *addnode(node *p, char *w){ if (p == NULL){ p = (NODE *) malloc (sizeof(node)); p -> word = walloc(w); p -> count = 1; p -> next = NULL; else if (strcmp(p -> word, w ) == 0) p -> count ++; else p -> next = addnode(p -> next, w); return p; ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 12 6
char *walloc(char *s){ char *p; H συνάρτηση walloc p = (char *) malloc (strlen(s)+1); if (p!= NULL) strcpy(p,s); return p; ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 13 Λύση 2 : Ταξινοµηµένη Λίστα Κατά το ψάξιµο µιας λέξης στη λίστα, της λύσης 1, εκτελούµε γραµµική διερεύνηση (δηλ. ξεκινούµε από αριστερά και προχωρούµε προς τα δεξιά µέχρι είτε να βρούµε τη ζητούµενη λέξη, είτε να φτάσουµε στο τέλος της λίστας). Αυτό δεν είναι και τόσο αποδοτικό. Θα µπορούσαµε να βελτιώσουµε την απόδοση του προγράµµατός µας διατηρώντας τη λίστα ταξινοµηµένη. Έτσι αν κατά τη διερεύνηση κάποιας λέξης βρούµε λέξη στη λίστα που είναι λεξικογραφικά µεγαλύτερη από αυτή που ψάχνουµε µπορούµε να συµπεράνουµε ότι η λέξη δεν βρίσκεται στη λίστα εποµένως δεν χρειάζεται να ελέγξουµε τα υπόλοιπα στοιχεία και η εισαγωγή κόµβου γίνεται άµεσα στο σηµείο που βρισκόµαστε. ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 14 7
H συνάρτηση addnode NODE *addnode(node *p, char * w){ NODE *q; int cond; if (p == NULL){ q = (NODE *)malloc(sizeof(node)); q -> word = walloc(w); q -> count = 1; q -> next = NULL; else if ( (cond = strcmp(p -> word, w)) == 0 ) p -> count++; else if (cond < 0){ q = p; q->next = addnode(q->next, w); ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 15 H συνάρτηση addnode else { q = (NODE *) malloc (sizeof(node)); q -> word = walloc(w); q -> count = 1; q -> next = p; return q; ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 16 8
ιπλά Συνδεδεµένες Λίστες ιπλά συνδεδεµένη λίστα (doubly-linked list) ονοµάζεται µια λίστα κάθε κόµβος της οποίας κρατά πληροφορίες και για τον επόµενο και για τον προηγούµενο κόµβο: prev next Με αυτό τον τρόπο δίνεται η ευχέρεια µετακίνησης µέσα στη λίστα και προς τις δύο κατευθύνσεις. Παράδειγµα Λίστας: size top 3 4 42 54 ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 17 ιπλά Συνδεδεµένες Λίστες Ποιες δοµές χρειάζονται για υλοποίηση µιας διπλά συνδεδεµένης λίστας; Ένας κόµβος ορίζεται από το πιο κάτω structure: typedef struct dlnode { int data; struct dlnode *prev; struct dlnode *next; DLNODE; O κόµβος που ορίζει την διπλά συνδεδεµένη λίστα είναι ο ίδιος µε αυτό που ορίζει µια στοίβα: typedef struct dllist { DLNODE *top; int size; DLLIST; ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 18 9
ιπλά Συνδεδεµένες Λίστες Προφανώς η εισαγωγή στοιχείου σε κάποιο σηµείο µιας διπλά συνδεδεµένης λίστας περιέχει κάποια επιπλέον πολυπλοκότητα από την εισαγωγή σε µια µονά-συνδεδεµένη λίστα. Αυτό γιατί κάθε νεοδηµιούργητος κόµβος πρέπει να συνδεθεί και µε τον επόµενο και µε τον προηγούµενο κόµβο στη λίστα. Παρόµοια, κατά τις εξαγωγές στοιχείων. Παράδειγµα εισαγωγής του στοιχείου 50 µετά το 42 στη λίστα της διαφάνειας 17: 3 4 42 54 50 ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 19 Η συνάρτηση put Να ορίσετε συνάρτηση put(l, x, y) η οποία τοποθετεί το στοιχείο xµετά από το στοιχείο yµέσα στη λίστα l. void put(dllist *l, int x, int y){ if (l->top == NULL) printf( The list is empty, no insertion was made\n ); else { putnode(l->top, int x, int y); ++(l->size); ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 20 10
Η συνάρτηση puttnode void putnode(dlnode *p, int x, int y){ DLNODE *q; if ( p->data == y ) { q = (DLNODE *)malloc(sizeof(dlnode)); q->data = x; q->next = p->next; q->prev = p; (p->next)->prev = q; p->next= q; else if (p == NULL) printf( Element %d does not exist, no insertion was made\n, y); else putnode(p->next, x, y); ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 21 Ταξινοµηµένες ιπλά Συνδεδεµένες Λίστες Άσκηση: Να ορίσετε διαδικασίες insert(dllist *l, x), η οποία εισάγει το στοιχείο xµέσα στη λίστα l, delete (DLLIST *l, x), η οποία αφαιρεί το στοιχείο y από τη λίστα l(αν αυτό υπάρχει) και printlist (DLLIST *l), η οποιά τυπώνει όλα τα στοιχεία της λίστας l. οι οποίες να διατηρούν ταξινοµηµένες διπλά συνδεδεµένες λίστες. ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 22 11
Παραδείγµατα Εισαγωγή του 50 στη λίστα της διαφάνειας 17: 3 4 42 54 50 Εξαγωγή του 42 από τη λίστα της διαφάνειας 17: 3 4 42 54 ΕΠΛ 132 Αρχές Προγραµµατισµού ΙΙ 23 12