ΠΛΗ111 ∆οµηµένος...
Transcript of ΠΛΗ111 ∆οµηµένος...
ΠΛΗ111∆οµηµένος Προγραµµατισµός
‘Ανοιξη 2005
Μάθηµα 3ο
Συνδεδεµένες Λίστες
Τµήµα Ηλεκτρονικών Μηχανικών και Μηχανικών ΥπολογιστώνΠολυτεχνείο Κρήτης
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 2
Ανασκόπηση
• Ο ΑΤ∆ λίστα• Ακολουθιακή λίστα• Συνδεδεµένη λίστα• Υλοποίηση συνδεδεµένης λίστας µε πίνακα• Υλοποίηση συνδεδεµένης λίστας µε δείκτες• Παραδείγµατα• Λίστα µε header• Λίστα µε sentinel• Κυκλική λίστα• ∆ιπλά συνδεδεµένη λίστα
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 3
ΑΤ∆ Λίστα
• Γραµµική συλλογή στοιχείων ίδιου τύπου– Έχει πρώτο και τελευταίο στοιχείο– Κάθε στοιχείο εκτός του πρώτου έχει ένα προηγούµενο– Κάθε στοιχείο εκτός του τελευταίου έχει ένα επόµενο
• Εφαρµογές: γραµµική διαχείριση πληροφοριών
στοιχείο1
πρώτο τελευταίο
στοιχείον-1 στοιχείον
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 4
ΑΤ∆ Λίστα (2)
• Υποστηρίζει τις πράξεις:– ∆ηµιουργία κενής λίστας– Εισαγωγή, διαγραφή και αναζήτηση συγκεκριµενου στοιχείου– Μήκος λίστας– Έλεγχος αν η λίστα είναι κενή– ...
στοιχείο1
πρώτο τελευταίο
στοιχείον-1 στοιχείον
στοιχείον+1 Π.χ. εισαγωγή στοιχείου
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 5
Ακολουθιακή Λίστα
• Υλοποίηση λίστας – Αποθηκεύει διαδοχικά στοιχεία σε γειτονικές θέσεις µνήµης– Βασίζεται σε πίνακα– ∆ιατηρεί το πλήθος των στοιχείων σε βοηθητική µεταβλητή
typedef struct { seqList_t list; int number; void init(seqList *list) {
element_t records[MaxElements]; list->number = 0; } seqList_t; }
a1 an
0 number-1 MaxElements-1
records
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 6
Εισαγωγή Στοιχείου 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++; }
number-1
MaxElements-1
pos
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 7
∆ιαγραφή Στοιχείου Ο(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--; }
number-1
MaxElements-1
pos
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 8
Μειονεκτήµατα Πίνακα
• Σταθερό µέγεθος που καθορίζεται κατά τη δήλωση ή τη δηµιουργία του πίνακα
• Συνήθως ο προγραµµατιστής δηλώνει «αρκετά µεγάλο» πίνακα που οδηγεί σε– Αχρησιµοποίητη µνήµη– Τερµατισµό προγράµµατος αν η πρόβλεψη έγινε λάθος
• Εισαγωγή στοιχείων στην αρχή του πίνακα ακριβή• Προσπέλαση στοιχείων απαιτεί υπολογισµό διεύθυνσης• Αλλά το κόστος διαχείρισης µνήµης χαµηλό
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 9
Απλή Συνδεδεµένη Λίστα
• Κάθε στοιχείο της λίστας καλείται κόµβος και περιέχει– ∆εδοµένα– ∆είκτη στον επόµενο κόµβο της λίστας
• Μια µεταβλητή δείχνει στον πρώτο κόµβο της λίστας
δεδοµένα
λίστα
κενή λίστα
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 10
Εισαγωγή Στοιχείου O(1)
• Τέσσερις δείκτες– head δείχνει στον πρώτο κόµβο– newNode δείχνει στο νέο κόµβο– current διατρέχει τους κόµβους– beforeCurrent δείχνει τον κόµβο αµέσως πριν τον current
head
current beforeCurrent
newNode
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 11
∆ιαγραφή Στοιχείου O(1)
• Τρεις δείκτες– headδείχνει στον πρώτο κόµβο– current διατρέχει τους κόµβους– beforeCurrent δείχνει τον κόµβο αµέσως πριν τον current
head
current beforeCurrent
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 12
Σύγκριση Κόστους Πράξεων
• Συνδεδεµένη λίστα– Εισαγωγή και διαγραφή κόµβου κοστίζει σταθερό χρόνο Ο(1)– Αναζήτηση στοιχείου κοστίζει γραµµικό χρόνο Ο(n)
• Ακολουθιακή λίστα– Εισαγωγή και διαγραφή κόµβου κοστίζει γραµµικό χρόνο Ο(n)– Αναζήτηση i-στού στοιχείου κοστίζει σταθερό χρόνο Ο(1)– Αναζήτηση µε βάση το περιέχοµενο κοστίζει γραµµικό χρόνο
O(n)
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 13
Στατική Συνδεδεµένη Λίστα
• Πίνακας σταθερού αριθµού δοµών typedef struct {
data_t data; /* δεδοµένα */int next; /* δείκτης στοιχείου πίνακα */
} node_t;node_t nodes[MaxNodes];
• Μειονεκτήµατα– Ανάγκη πρόβλεψης του αριθµού κόµβων πριν την εκτέλεση– ∆έσµευση µνήµης για όλους τους κόµβους κατά την εκτέλεση– ...
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 14
Αρχικοποίηση Πίνακα
/* λίστα διαθέσιµων κόµβων */ for (int i = 0; i < MaxNodes –1 ; i++ ) nodes[i].next = i + 1; nodes[MaxNodes-1].next = -1; /* πρώτος διαθέσιµος κόµβος */ freeNode= 0; /* κενή λίστα */ int head = -1;
data next
1
2
3
4
5
6
-1
1
2
3
4
5
6
0
freeNode
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 15
∆έσµευση & Αποδέσµευση Κόµβου
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; }
data next
1
2
3
4
5
6
-1
1
2
3
4
5
6
0
freeNode
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 16
Εισαγωγή Κόµβου 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; } } }
1
-1
3
4
5
6
-1
1
2
3
4
5
6
0
freeNode headPtr
current
newNode
d
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 17
∆ιαγραφή Κόµβου void delAfter(int *headPtr, int b, data_t *d) { int c; 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); } }
2
-1
1
4
5
6
-1
1
2
3
4
5
6
0 freeNode
headPtr
current
beforeCurrent
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 18
Αναζήτηση Κόµβου
int find(int head, data_t d) { while (head > -1 && nodes[head].data != d) head = nodes[head].next; return (head); } int findi(int head, int i) { if (i < 1) return (-1); while (head > -1 && --i) head = nodes[head].next; return (head); }
2
-1
1
4
5
6
-1
1
2
3
4
5
6
0
head
freeNodes
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 19
∆υναµική Συνδεδεµένη Λίστα
• ∆υναµική καταχώρηση µνήµης για δηµιουργία κόµβων• Ορίζεται µε αυτοαναφορική (ή αναδροµική) δοµή
/* δήλωση τύπου */typedef struct node {
data_t data;struct node *next;
} node_t, *nodePtr_t;
/* ορισµός λίστας */nodePtr_t head = NULL;
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 20
∆έσµευση και Αποδέσµευση Κόµβου
/* δέσµευση µνήµης κόµβου */ nodePtr_t getNode(data_t d) {
nodePtr_t newNode; newNode = (nodePtr_t)
malloc(sizeof(node_t)); newNode->data = d; return (newNode);
}
/* αποδέσµευση µνήµης κόµβου */ void relNode(nodePtr_t oldNode) {
free(oldNode); }
newNode data
next
Heap
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 21
Εισαγωγή Κόµβου
void insAfter(nodePtr_t *headPtr, nodePtr_t b, data_t d) {
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;
} }
headPtr
newNode
beforeCurrent
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 22
∆ιαγραφή Κόµβου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 © Στέργιος Β. Αναστασιάδης 23
Μήκος Λίστας
head
current
• ∆ίνεται δείκτης στον πρώτο κόµβο της συνδεδεµένης λίστας (head)
• Να υπολογιστεί το µήκος της λίστας (length)
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 24
Λύση
int length(nodePtr_t head) {
nodePtr_t current = head; int count = 0; while (current != NULL) {
count++; current = current->next;
} return count;
}
head
current
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 25
Αντιγραφή Λίστας
• ∆ίνεται δείκτης σε συνδεδεµένη λίστα
• Να δηµιουργηθεί ένα νέο πλήρες αντίγραφο της λίστας
head head
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 26
ΛύσηnodePtr_t CopyList(nodePtr_t head) {
/* αρχική λίστα */ nodePtr_t current = head; /* νέα λίστα */ nodePtr_t newList = NULL; nodePtr_t tail = NULL;
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);
}
headnewList
tail
current
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 27
∆ιαγραφή Λίστας
• ∆ίνεται δείκτης στον πρώτο κόµβο µίας συνδεδεµένης λίστας
• Να διαγραφούν όλοι οι κόµβοι από την λίστα και να αποδεσµευτεί η µνήµη που καταλαµβάνουν
headcurrent
head
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 28
Λύση
void deleteList(nodePtr_t *headRef) {
nodePtr_t current = *headRef; nodePtr_t next; while (current != NULL) {
next = current->next; relNode(current); current = next;
} *headRef = NULL;
}
headcurrent
next
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 29
Συνδεδεµένη Λίστα µε Header
• Η υλοποίηση πράξεων της απλής συνδεδεµένης λίστας – Συχνά απαιτεί τροποποίηση του δείκτη στον πρώτο κόµβο– Εισάγει επιπλέον ειδικές περιπτώσεις στις συναρτήσεις– Αυξάνει το επίπεδο έµµεσης αναφοράς σε δύο επίπεδα
head
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 30
Συνδεδεµένη Λίστα µε Header (2)
• Μια λύση είναι η χρήση κενού κόµβου (header) στην αρχή της λίστας
• Απλοποιεί επαναληπτικές διεργασίες– Αποθηκεύει επιπλέον χρήσιµες πληροφορίες όπως το µήκος της λίστας, δείκτη στον τελευταίο κόµβο, κτλ
– Αλλά ξοδεύει τη µνήµη ενός κόµβου ακόµη και για κενή λίστα
header
head
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 31
Εισαγωγή κόµβου σε λίστα µε 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;
} }
newNode
beforeCurrent
header
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 32
∆ιαγραφή κόµβου από λίστα µε 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 © Στέργιος Β. Αναστασιάδης 33
Συνδεδεµένη Λίστα µε Sentinel
• Η αναζήτηση σε απλή συνδεδεµένη λίστα συνήθως τερµατίζεται από δείκτη NULL
• Εναλλακτικά µπορούµε να έχουµε κάποια ειδική τιµή δεδοµένων για τερµατισµό στον τελευταίο κόµβο
• Απλοποιούµε τον βρόχο αναζήτησης µε λιγότερες συγκρίσεις
+oo
head sentinel
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 34
Παράδειγµα
• Θεωρήστε ακολουθία αριθµών σε αύξουσα σειρά αποθηκευµένη σε συνδεδεµένη λίστα
• Εξετάζουµε εναλλακτικούς τρόπους αναζήτησης
/* απλή συνδεδεµένη λίστα /* λίστα µε sentinel +oo µε αύξουσα διάταξη */ τερµατίζει µε +oo > n */ while (p && p->data < n) while (p->data < n) p=p->next; p=p->next;
+oo
head sentinel
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 35
Κυκλική Λίστα
• Απλή συνδεδεµένη λίστα της οποίας ο τελευταίος κόµβος δείχνει στον πρώτο
• Με δείκτη σε οποιονδήποτε κόµβο της λίστας µπορούµε να φτάσουµε σε όλους τους υπόλοιπους
• Φυσική αναπαράσταση για συγκεκριµένες εφαρµογές– Η περιφέρεια πολυγώνου µπορεί να παρασταθεί µε κυκλική λίστα των κόµβων του πολυγώνου
head
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 36
Εισαγωγή Κόµβου σε Κυκλική Λίστα
• ∆εδοµένης της κυκλικότητας δε χρειάζεται να τροποποιήσουµε το δείκτη στον πρώτο κόµβο παρά µόνο όταν εισάγουµε κόµβο σε κενή λίστα
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;
} }
newNode
beforeCurrent
head
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 37
∆ιαδροµή Κυκλικής Λίστας
• Αν η λίστα ενδέχεται να είναι κενή χρειάζεται ειδική αντιµετώπιση µε έλεγχο if (head == NULL)
• Αλλιώς µπορούµε να χρησιµοποιήσουµε το βρόχοop = p = headdo {
/* επεξεργασία p */…p = p->next
} while (p != op) /*συνθήκη τερµατισµού*/
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 38
∆ιπλά Συνδεδεµένη Λίστα
• Κάθε κόµβος έχει δείκτη τόσο στον προηγούµενο όσο και στον επόµενο κόµβο– Από ένα κόµβο έχουµε άµεση πρόσβαση στους δύο γειτονικούς σε σταθερό χρόνο Ο(1)
– Εισάγουµε κόµβο πριν ή µετά από κόµβο µε ένα δείκτη– ∆ιαγράψουµε κόµβο µε ένα δείκτη– ∆ιατρέχουµε τη λίστα σε δύο κατευθύνσεις
• Μειονεκτήµατα– Χρησιµοποιεί διπλάσιο αριθµό δεικτών από την απλή– Αυξάνει την πιθανότητα σφάλµατος δεικτών
head
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 39
∆ήλωση ∆ιπλά Συνδεδεµένης Λίστας
• Χρειαζόµαστε δύο δείκτες σε κάθε κόµβο που να δείχνουν στον προηγούµενο και τον επόµενο κόµβο
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 © Στέργιος Β. Αναστασιάδης 40
Αρχικοποίηση ∆ιπλά Συνδεδεµένης Λίστας
• Θεωρούµε διπλά συνδεδεµένη λίστα µε 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 © Στέργιος Β. Αναστασιάδης 41
Εισαγωγή Κόµβου
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;
} }
newNode
beforeCurrent
header
head
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 42
Υλοποίηση Συνόλου µε Λίστα
• Χρησιµοποιούµε απλή συνδεδεµένη λίστα ταξινοµηµένων στοιχείων:
– empty(s) : ελέγχει αν το σύνολο s είναι κενό– dumpset(s) : τυπώνει τα µέλη του συνόλου– member(s, n) : ελέγχει αν το n είναι στοιχείο του s– insert(s, n) : εισάγει το στοιχείο n στο s– unite(s1, s2) : επιστρέφει την ένωση των s1 και s2
typedef struct element element;struct element {
element *next;int value;
};typedef element *set;
#define empty(s) (s == NULL)
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 43
Υλοποίηση Πράξεων Συνόλου
/* τυπώνει τα µέλη του συνόλου */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 © Στέργιος Β. Αναστασιάδης 44
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;
}
new
p
dummy
’Ανοιξη 2005 © Στέργιος Β. Αναστασιάδης 45
Ένωση Συνόλων/* εισάγει το στοιχείο 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;
}