ΠΛΗ111 οµηµένος Προγραµµατισµός Ανοιξη 2005 Μάθηµα 3 ο Συνδεδεµένες Λίστες Τµήµα Ηλεκτρονικών Μηχανικών και Μηχανικών Υπολογιστών Πολυτεχνείο Κρήτης
Ανασκόπηση ΟΑΤ λίστα Ακολουθιακή λίστα Συνδεδεµένη λίστα Υλοποίηση συνδεδεµένης λίστας µε πίνακα Υλοποίηση συνδεδεµένης λίστας µε δείκτες Παραδείγµατα Λίστα µε header Λίστα µε sentinel Κυκλική λίστα ιπλά συνδεδεµένη λίστα Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 2
ΑΤ Λίστα Γραµµική συλλογή στοιχείων ίδιου τύπου Έχει πρώτο και τελευταίο στοιχείο Κάθε στοιχείο εκτός του πρώτου έχει ένα προηγούµενο Κάθε στοιχείο εκτός του τελευταίου έχει ένα επόµενο Εφαρµογές: γραµµική διαχείριση πληροφοριών στοιχείο 1 στοιχείο ν-1 στοιχείο ν πρώτο τελευταίο Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 3
Υποστηρίζει τις πράξεις: ΑΤ Λίστα (2) ηµιουργία κενής λίστας Εισαγωγή, διαγραφή και αναζήτηση συγκεκριµενου στοιχείου Μήκος λίστας Έλεγχος αν η λίστα είναι κενή... Π.χ. εισαγωγή στοιχείου στοιχείο ν+1 στοιχείο 1 στοιχείο ν-1 στοιχείο ν πρώτο τελευταίο Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 4
Υλοποίηση λίστας Ακολουθιακή Λίστα Αποθηκεύει διαδοχικά στοιχεία σε γειτονικές θέσεις µνήµης Βασίζεται σε πίνακα ιατηρεί το πλήθος των στοιχείων σε βοηθητική µεταβλητή typedef struct { seqlist_t list; int number; void init(seqlist *list) { element_t records[maxelements]; list->number = 0; seqlist_t; a 1 a n records 0 number-1 MaxElements-1 Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 5
Εισαγωγή Στοιχείου O(n) void insertafter(seqlist_t *listptr, element_t elem, int pos) { /* number < MaxElements && pos >= 0 && pos <= number 1 */ /* µετακίνηση στοιχείων δεξιά κατά 1 θέση*/ for (int i = listptr->number 1; i >= pos + 1; i--) listptr->records[i+1] = listptr->records[i]; listptr->records[pos+1] = elem; listptr->number++; pos number-1 MaxElements-1 Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 6
ιαγραφή Στοιχείου Ο(n) void delete(seqlist_t *listptr, int pos) { /* pos >= 0 && pos <= number 1 */ /* µετακίνηση στοιχείων αριστερά κατά 1 θέση*/ for (int i = pos; i <= listptr->number 1; i++) listptr->records[i] = listptr->records[i+1]; listptr->number--; pos number-1 MaxElements-1 Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 7
Μειονεκτήµατα Πίνακα Σταθερό µέγεθος που καθορίζεται κατά τη δήλωση ή τη δηµιουργία του πίνακα Συνήθως ο προγραµµατιστής δηλώνει «αρκετά µεγάλο» πίνακα που οδηγεί σε Αχρησιµοποίητη µνήµη Τερµατισµό προγράµµατος αν η πρόβλεψη έγινε λάθος Εισαγωγή στοιχείων στην αρχή του πίνακα ακριβή Προσπέλαση στοιχείων απαιτεί υπολογισµό διεύθυνσης Αλλά το κόστος διαχείρισης µνήµης χαµηλό Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 8
Απλή Συνδεδεµένη Λίστα Κάθε στοιχείο της λίστας καλείται κόµβος και περιέχει εδοµένα είκτη στον επόµενο κόµβο της λίστας Μια µεταβλητή δείχνει στον πρώτο κόµβο της λίστας κενή λίστα λίστα δεδοµένα Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 9
Τέσσερις δείκτες Εισαγωγή Στοιχείου O(1) head δείχνει στον πρώτο κόµβο newnode δείχνει στο νέο κόµβο current διατρέχει τους κόµβους beforecurrent δείχνει τον κόµβο αµέσως πριν τον current newnode head beforecurrent current Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 10
Τρεις δείκτες ιαγραφή Στοιχείου O(1) headδείχνει στον πρώτο κόµβο current διατρέχει τους κόµβους beforecurrent δείχνει τον κόµβο αµέσως πριν τον current head beforecurrent current Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 11
Σύγκριση Κόστους Πράξεων Συνδεδεµένη λίστα Εισαγωγή και διαγραφή κόµβου κοστίζει σταθερό χρόνο Ο(1) Αναζήτηση στοιχείου κοστίζει γραµµικό χρόνο Ο(n) Ακολουθιακή λίστα Εισαγωγή και διαγραφή κόµβου κοστίζει γραµµικό χρόνο Ο(n) Αναζήτηση i-στού στοιχείου κοστίζει σταθερό χρόνο Ο(1) Αναζήτηση µε βάση το περιέχοµενο κοστίζει γραµµικό χρόνο O(n) Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 12
Στατική Συνδεδεµένη Λίστα Πίνακας σταθερού αριθµού δοµών typedef struct { data_t data; /* δεδοµένα */ int next; /* δείκτης στοιχείου πίνακα */ node_t; node_t nodes[maxnodes]; Μειονεκτήµατα Ανάγκη πρόβλεψης του αριθµού κόµβων πριν την εκτέλεση έσµευση µνήµης για όλους τους κόµβους κατά την εκτέλεση... Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 13
Αρχικοποίηση Πίνακα freenode /* λίστα διαθέσιµων κόµβων */ for (int i = 0; i < MaxNodes 1 ; i++ ) nodes[i].next = i + 1; nodes[maxnodes-1].next = -1; /* πρώτος διαθέσιµος κόµβος */ freenode= 0; 0 1 2 3 data next 1 2 3 4 /* κενή λίστα */ int head = -1; 4 5 5 6 6-1 Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 14
έσµευση & Αποδέσµευση Κόµβου freenode int getnode() { int c; If (freenode == -1) return (-1) /* υπερχείλιση */ c = freenode; freenode = nodes[freenode].next; return (c); void relnode(int c) { nodes[c].next = freenode; freenode = c; 0 1 2 3 4 5 data next 1 2 3 4 5 6 6-1 Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 15
Εισαγωγή Κόµβου freenode headptr void insafter(int *headptr, int b, data_t d) { int n; if ((n = getnode()) < 0) printf( υπερχείλιση ); else { nodes[n].data = d; if (b < 0) { /* αρχή λίστας */ nodes[n].next = *headptr; *headptr = n; else { /* ενδιάµεσα */ nodes[n].next = nodes[b].next; nodes[b].next = n; 0 1 2 3 4 5 6 d 1-1 3 4 5 6-1 current newnode Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 16
ιαγραφή Κόµβου void delafter(int *headptr, int b, data_t *d) { int c; freenode if (b < 0) { /*διαγραφή πρώτου κόµβου*/ if (*headptr >= 0) { c = *headptr; *headptr = nodes[c].next; *d = nodes[c].data; relnode(c); else printf( κενή λίστα ); else if (nodes[b].next < 0) printf( άκυρη διαγραφή ); else { /* διαγραφή ενδιάµεσου κόµβου */ c = nodes[b].next; *d = nodes[c].data; nodes[b].next = nodes[c].next; relnode(c); 0 1 2 3 4 5 6 headptr 2-1 1 4 5 6-1 beforecurrent current Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 17
Αναζήτηση Κόµβου int find(int head, data_t d) { while (head > -1 && nodes[head].data!= d) head = nodes[head].next; return (head); 0 freenodes 1 int findi(int head, int i) { if (i < 1) return (-1); while (head > -1 && --i) head = nodes[head].next; 2 3 4 head 2-1 1 4 5 return (head); 5 6 6-1 Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 18
υναµική Συνδεδεµένη Λίστα υναµική καταχώρηση µνήµης για δηµιουργία κόµβων Ορίζεται µε αυτοαναφορική (ή αναδροµική) δοµή /* δήλωση τύπου */ typedef struct node { data_t data; struct node *next; node_t, *nodeptr_t; /* ορισµός λίστας */ nodeptr_t head = NULL; Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 19
έσµευση και Αποδέσµευση Κόµβου /* δέσµευση µνήµης κόµβου */ nodeptr_t getnode(data_t d) { nodeptr_t newnode; newnode = (nodeptr_t) malloc(sizeof(node_t)); newnode->data = d; return (newnode); newnode data next /* αποδέσµευση µνήµης κόµβου */ void relnode(nodeptr_t oldnode) { free(oldnode); Heap Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 20
Εισαγωγή Κόµβου void insafter(nodeptr_t *headptr, nodeptr_t b, data_t d) { headptr nodeptr_t n; if ((n=getnode(d)) == NULL) printf( κενός κόµβος ); else if (b == NULL) { /* πριν τον πρώτο κόµβο */ n->next = *headptr; *headptr = n; else { /* ενδιάµεσα */ n->next = b->next; b->next = n; beforecurrent newnode Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 21
ιαγραφή Κόµβου void delafter(nodeptr_t *headptr, nodeptr_t b, data_t *d) { nodeptr_t c; if (b == NULL) { /* διαγραφή πρώτου κόµβου */ if (*headptr == NULL) printf( κενή λίστα ); else { c = *headptr; *headptr = c->next; *d = c->data; relnode(c); else if (b->next == NULL) printf( άκυρη διαγραφή ); else { c = b->next; *d = c->data; b->next = c->next; relnode(c); headptr beforecurrent Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 22
Μήκος Λίστας ίνεται δείκτης στον πρώτο κόµβο της συνδεδεµένης λίστας (head) Να υπολογιστεί το µήκος της λίστας (length) head current Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 23
Λύση head int length(nodeptr_t head) { nodeptr_t current = head; int count = 0; while (current!= NULL) { count++; current = current->next; current return count; Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 24
Αντιγραφή Λίστας ίνεται δείκτης σε συνδεδεµένη λίστα Να δηµιουργηθεί ένα νέο πλήρες αντίγραφο της λίστας head head Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 25
Λύση nodeptr_t CopyList(nodePtr_t head) { /* αρχική λίστα */ nodeptr_t current = head; /* νέα λίστα */ nodeptr_t newlist = NULL; nodeptr_t tail = NULL; current head newlist while (current!= NULL) { if (newlist == NULL) { newlist = getnode(current->data); newlist->next = NULL; tail = newlist; else { tail->next = getnode(current->data); tail = tail->next; tail->next = NULL; current = current->next; return(newlist); tail Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 26
ιαγραφή Λίστας ίνεται δείκτης στον πρώτο κόµβο µίας συνδεδεµένης λίστας Να διαγραφούν όλοι οι κόµβοι από την λίστα και να αποδεσµευτεί η µνήµη που καταλαµβάνουν current head head Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 27
Λύση void deletelist(nodeptr_t *headref) { nodeptr_t current = *headref; nodeptr_t next; current head while (current!= NULL) { next = current->next; relnode(current); current = next; *headref = NULL; next Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 28
Συνδεδεµένη Λίστα µε Header Η υλοποίηση πράξεων της απλής συνδεδεµένης λίστας Συχνά απαιτεί τροποποίηση του δείκτη στον πρώτο κόµβο Εισάγει επιπλέον ειδικές περιπτώσεις στις συναρτήσεις Αυξάνει το επίπεδο έµµεσης αναφοράς σε δύο επίπεδα head Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 29
Συνδεδεµένη Λίστα µε Header (2) Μια λύση είναι η χρήση κενού κόµβου (header) στην αρχή της λίστας Απλοποιεί επαναληπτικές διεργασίες Αποθηκεύει επιπλέον χρήσιµες πληροφορίες όπως το µήκος της λίστας, δείκτη στον τελευταίο κόµβο, κτλ Αλλά ξοδεύει τη µνήµη ενός κόµβου ακόµη και για κενή λίστα header head Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 30
Εισαγωγή κόµβου σε λίστα µε Header void insertafter(nodeptr_t b, data_t d) { nodeptr n; if ((n=getnode(d)) == NULL) printf( κενός κόµβος ); else { /* ενδιάµεσα */ n->next = b->next; b->next = n; header beforecurrent newnode Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 31
ιαγραφή κόµβου από λίστα µε Header void delafter(nodeptr_t b, data_t *d) { nodeptr_t c; if (b->next == NULL) printf( άκυρη διαγραφή ); else { c = b->next; *d = c->data; b->next = c->next; relnode(c); header beforecurrent Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 32
Συνδεδεµένη Λίστα µε Sentinel Ηαναζήτηση σε απλή συνδεδεµένη λίστα συνήθως τερµατίζεται από δείκτη NULL Εναλλακτικά µπορούµε να έχουµε κάποια ειδική τιµή δεδοµένων για τερµατισµό στον τελευταίο κόµβο Απλοποιούµε τον βρόχο αναζήτησης µε λιγότερες συγκρίσεις head sentinel +oo Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 33
Παράδειγµα Θεωρήστε ακολουθία αριθµών σε αύξουσα σειρά αποθηκευµένη σε συνδεδεµένη λίστα Εξετάζουµε εναλλακτικούς τρόπους αναζήτησης /* απλή συνδεδεµένη λίστα /* λίστα µε sentinel +oo µε αύξουσα διάταξη */ τερµατίζει µε +oo > n */ while (p && p->data < n) while (p->data < n) p=p->next; p=p->next; head sentinel +oo Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 34
Κυκλική Λίστα Απλή συνδεδεµένη λίστα της οποίας ο τελευταίος κόµβος δείχνει στον πρώτο Με δείκτη σε οποιονδήποτε κόµβο της λίστας µπορούµε να φτάσουµε σε όλους τους υπόλοιπους Φυσική αναπαράσταση για συγκεκριµένες εφαρµογές Η περιφέρεια πολυγώνου µπορεί να παρασταθεί µε κυκλική λίστα των κόµβων του πολυγώνου head Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 35
Εισαγωγή Κόµβου σε Κυκλική Λίστα εδοµένης της κυκλικότητας δε χρειάζεται να τροποποιήσουµε το δείκτη στον πρώτο κόµβο παρά µόνο όταν εισάγουµε κόµβο σε κενή λίστα void insertafter(nodeptr_t *headptr, nodeptr_t nodeptr b, data_t d) { nodeptr n; if ((n=getnode(d)) == NULL) printf( κενός κόµβος ); else if (*headptr == NULL) { n->next = n; /* κενή λίστα */ *headptr = n; else { /* ενδιάµεσα */ n->next = b->next; b->next = n; head beforecurrent newnode Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 36
ιαδροµή Κυκλικής Λίστας Αν η λίστα ενδέχεται να είναι κενή χρειάζεται ειδική αντιµετώπιση µε έλεγχο if (head == NULL) Αλλιώς µπορούµε να χρησιµοποιήσουµε το βρόχο op = p = head do { /* επεξεργασία p */ p = p->next while (p!= op) /*συνθήκη τερµατισµού*/ Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 37
ιπλά Συνδεδεµένη Λίστα Κάθε κόµβος έχει δείκτη τόσο στον προηγούµενο όσο και στον επόµενο κόµβο Από ένα κόµβο έχουµε άµεση πρόσβαση στους δύο γειτονικούς σε σταθερό χρόνο Ο(1) Εισάγουµε κόµβο πριν ή µετά από κόµβο µε ένα δείκτη ιαγράψουµε κόµβο µε ένα δείκτη ιατρέχουµε τη λίστα σε δύο κατευθύνσεις Μειονεκτήµατα Χρησιµοποιεί διπλάσιο αριθµό δεικτών από την απλή Αυξάνει την πιθανότητα σφάλµατος δεικτών head Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 38
ήλωση ιπλά Συνδεδεµένης Λίστας Χρειαζόµαστε δύο δείκτες σε κάθε κόµβο που να δείχνουν στον προηγούµενο και τον επόµενο κόµβο struct node { data_t data; struct node *prev, *next; node_t, *nodeptr_t; Εναλλακτικά µπορούµε να χρησιµοποιήσουµε array δύο στοιχείων για εύκολη υλοποίηση αµφίδροµης διαδροµής struct node { data_t data; struct node *link[2]; node_t, *nodeptr_t; Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 39
Αρχικοποίηση ιπλά Συνδεδεµένης Λίστας Θεωρούµε διπλά συνδεδεµένη λίστα µε header void create(nodeptr_t *headptr) { nodeptr_t header = (nodeptr_t) malloc(sizeof(node_t)); header->prev = header->next = NULL; *headptr = header; head header Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 40
Εισαγωγή Κόµβου void insertafter(nodeptr_t b, data_t d) { nodeptr n; if ((n=getnode(d)) == NULL) printf( κενός κόµβος ); else { /* ενδιάµεσα */ n->prev = b; n->next = b->next; if (b->next!= NULL) b->next->prev = n; b->next = n; header head newnode beforecurrent Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 41
Υλοποίηση Συνόλου µε Λίστα Χρησιµοποιούµε απλή συνδεδεµένη λίστα ταξινοµηµένων στοιχείων: empty(s) : ελέγχει αν το σύνολο s είναι κενό dumpset(s) : τυπώνει τα µέλη του συνόλου member(s, n) : ελέγχει αν το n είναι στοιχείο του s insert(s, n) : εισάγει το στοιχείο n στο s unite(s 1, s 2 ) : επιστρέφει την ένωση των s 1 και s 2 typedef struct element element; struct element { element *next; int value; ; typedef element *set; #define empty(s) (s == NULL) Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 42
Υλοποίηση Πράξεων Συνόλου /* τυπώνει τα µέλη του συνόλου */ void dumpset(set s) { while (s) { printf( %d, s->value); s = s->next; /* ελέγχει αν το n είναι µέλος του s */ int member(set s, int n) { while (s && s->value < n) s = s->next; return s && s->value == n; Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 43
Eισαγωγή Στοιχείου σε Σύνολο /* εισάγει το στοιχείο n στο σύνολο s */ set insert(set s, int n) { element dummy, *p, *new; p = &dummy; p->next = s; while (p->next && p->next->value < n) p = p->next; if (!(p->next && p->next->value == n)) { /* το n δεν είναι µέλος του συνόλου */ new = (element *) malloc(sizeof(element)); new->value = n; new->next = p->next; p->next = new; return dummy.next; dummy p new Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 44
Ένωση Συνόλων /* εισάγει το στοιχείο n στο σύνολο s */ set unite(set s1, set s2) { element dummy, *p; p = &dummy; while (s1 && s2) { if (s1->value < s2->value) p->next=getnode(s1->value); s1=s1->next; else if (s2->value < s1->value) { p-> next = getnode(s2->value); s2 = s2->next; else { /* ίδια µέλη */ p->next = getnote(s1->value); s1 = s1->next; s2 = s2->next; p = p->next; /* αντιγραφή υπολοίπου συνόλου */ if (!s1) s1 = s2; while (s1) { p->next = getnode(s1->value); p = p->next; s1 = s1->next; return dummy.next; /* δηµιουργία νέου κόµβου */ element *getnode(int n) { element *p = (element *) malloc(sizeof(element)); p->value = n; return p; Ανοιξη 2005 Στέργιος Β. Αναστασιάδης 45