Γλώσσα C & Unix Τμήμα Πληροφορικής, ΑΠΘ B’ εξάμηνο Δυναμικές Δομές Γλώσσα C & Unix Τμήμα Πληροφορικής, ΑΠΘ B’ εξάμηνο lpis.csd.auth.gr/curriculum/C+Unix/UNCL202.html
Μέτρηση Συχνότητας Εμφάνισης Λέξεων σε Κείμενο Θέλουμε να μετρήσουμε τη συχνότητα εμφά-νισης όλων των λέξεων μέσα σε ένα κείμενο Η λίστα των λέξεων δεν είναι γνωστή εκ των προτέρων Δεν μπορούμε να την ταξινομήσουμε από πριν και να χρησιμοποιήσουμε δυαδική αναζήτηση Δεν μπορούμε να κάνουμε σειριακή αναζήτηση για κάθε καινούρια λέξη, γιατί το πρόγραμμα θα καθυστερεί δραματικά Πώς μπορούν να οργανωθούν τα δεδομένα για γρήγορη αναζήτηση τυχαίων συνόλων λέξεων;
Λύση Προβλήματος Οι λέξεις που έχουν συναντηθεί στο κείμενο να βρίσκονται συνεχώς ταξινομημένες Κάθε νέα λέξη πρέπει να τοποθετείται στη σωστή «λεξικογραφική» σειρά Δεν πρέπει να γίνεται μετακίνηση λέξεων μέσα σε ένα γραμμικό πίνακα Είναι πολύ αργή Θα χρησιμοποιηθούν δυναμικές δομές δεδομένων Συνδεδεμένες Λίστες Δυαδικά Δένδρα
Συνδεδεμένες Λίστες Μια από τις πιο βασικές δομές δεδομένων Αποτελούνται από πολλά στοιχεία τα οποία οργανώνονται ή συνδέονται μεταξύ τους με μια συγκεκριμένη σειρά Χρησιμοποιούνται για ομαδοποίηση δεδομένων, όπως οι πίνακες
Συνδεδεμένες Λίστες - Πλεονεκτήματα Πιο αποδοτικές κατά την εισαγωγή και διαγραφή στοιχείων Δυναμική χρήση της μνήμης Σε πολλές εφαρμογές το μέγεθος της συλλογής των δεδομένων δεν είναι γνωστό εκ των προτέρων Απλά συνδεδεμένες λίστες: Τα στοιχεία συνδέονται με 1 δείκτη Η λίστα διατρέχεται από το πρώτο προς το τελευταίο στοιχείο
Απλά Συνδεδεμένη Λίστα Δείκτης επόμενου στοιχείου Δεδομένα NULL Στοιχείο λίστας Δείκτης στο πρώτο στοιχείο της λίστας Κάθε στοιχείο της λίστας είναι μία δομή (struct) Τα δεδομένα μπορεί να είναι 1 ή και περισσότερα στοιχεία (μέλη) της δομής
Αναπαράσταση της Λίστας με Δομή struct node { int data; ... struct node *next; }; Αυτό-αναφορική ή αναδρομική δομή Δομή που περιέχει τον εαυτό της Στην πραγματικότητα η δομή node δεν περιέχει όμοια δομή, αλλά έναν δείκτη σε όμοια δομή
Χρήση Λίστας για τη Μέτρηση Συχνότητας Εμφάνισης Λέξεων σε Κείμενο Σε κάθε στοιχείο της λίστας αποθη-κεύεται μία λέξη και ένας μετρητής struct node { char *word; int count; struct node *next; };
Χρήση Λίστας για τη Μέτρηση Συχνότητας Εμφάνισης Λέξεων σε Κείμενο Η λέξεις τοποθετούνται στη λίστα ταξινομημένες Όταν το πρόγραμμα συναντάει μία νέα λέξη την τοποθετεί στο «σωστό» σημείο Όταν το πρόγραμμα συναντάει μία υπάρχουσα λέξη αυξάνει τον μετρητή κατά ένα
Τοποθέτηση Νέας Λέξης Είναι πολύ εύκολη η εισαγωγή νέου στοιχείου σε οποιοδήποτε σημείο της λίστας, χωρίς μετακίνηση δεδομένων στη μνήμη Βλ. πίνακες Σημείο εισαγωγής Αρχική λίστα NULL Στοιχείο για εισαγωγή Τελική λίστα NULL
Κανόνες Τοποθέτησης Νέας Λέξης Συγκρίνεται λεξικογραφικά η νέα λέξη με τις υπάρχουσες σε κάθε στοιχείο-κόμβο της λίστας Εάν η νέα λέξη είναι «μεγαλύτερη» από την λέξη του τρέχοντος στοιχείου, τότε πρέπει να τοποθετηθεί πιο δεξιά Ακολουθείται ο δείκτης στο επόμενο στοιχείο Εάν η νέα λέξη είναι ίδια, τότε αυξάνεται ο μετρητής
Κανόνες Τοποθέτησης Νέας Λέξης Εάν η νέα λέξη είναι «μικρότερη», τότε πρέπει να τοποθετηθεί αμέσως πριν από το τρέχον στοιχείο Αν το τρέχον στοιχείο είναι το τέλος της λίστας (NULL), τότε η νέα λέξη τοποθετείται στο τέλος της λίστας
Δημιουργία Νέας Λέξης Δημιουργείται μία νέα δομή που περιλαμβάνει τη λέξη και μετρητή 1 Γίνεται η κατάλληλη σύνδεση με το προηγούμενο και επόμενο στοιχείο της λίστας Μέσω δεικτών
Κύριο Πρόγραμμα (main) void main () { struct node *list = NULL; char word[MAXWORD]; while (getword(word, MAXWORD) != EOF) if (isalpha(word[0])) list = insert(list,word); printlist(list); } Κάθε λέξη τοποθετείται στη σωστή σειρά Τυπώνεται η λίστα (ταξινομημένη)
Εκτύπωση Στοιχείων Λίστας void printlist (struct node *list) { if (list != NULL) { printf("%4d %s\n", list->count, list->word); printlist(list->next); } Τερματισμός αναδρομής Εκτύπωση των δεδομένων του τρέχοντος στοιχείου Εκτύπωση των επόμενων στοιχείων Αναδρομική κλήση
Εισαγωγή Λέξης στη Λίστα struct node *insert(struct node *p, char *w) { struct node *n; int cond; if (p == NULL) { p = alloc(); p->word=strdup(w); p->count = 1; p->next = NULL; } Αν ο δείκτης δείχνει σε NULL δείκτη Φτάσαμε στο τέλος της λίστας και η λέξη δεν βρέθηκε Πρέπει να προστεθεί νέο στοιχείο στο τέλος της λίστας Δέσμευση μνήμης Αποθήκευση λέξης Αποθήκευση μετρητή 1 Αφού το νέο στοιχείο είναι το τελευταίο της λίστας, το επόμενο είναι NULL
Τοποθέτηση Νέας Λέξης σε Άδεια Λίστα Σημείο εισαγωγής Αρχική λίστα Στοιχείο για εισαγωγή NULL Αλλάζει ο δείκτης προς τη λίστα! Τελική λίστα NULL
Εισαγωγή Λέξης στη Λίστα else if ((cond=strcmp(w, p->word)) == 0) p->count++; else if (cond < 0) { n = alloc(); n->word = strdup(w); n->count = 1; n->next = p; p = n; } Σύγκρινε την λέξη προς εισαγωγή με την αποθηκευμένη λέξη Δημιουργία στοιχείου και αποθήκευση δεδομένων Αν είναι ίδιες, τότε αύξησε τον μετρητή κατά 1 Αν είναι μικρότερη, τότε πρόσθεσέ την στη λίστα πριν από το τρέχον στοιχείο Το νέο στοιχείο δείχνει το τρέχον στοιχείο ως επόμενό του, αφού προηγείται Το νέο στοιχείο είναι πλέον το τρέχον στοιχείο της λίστας
Τοποθέτηση Νέας Λέξης στην Πρώτη Θέση της Λίστας Σημείο εισαγωγής Στοιχείο για εισαγωγή NULL Αρχική λίστα NULL Αλλάζει ο δείκτης προς τη λίστα! Τελική λίστα
Εισαγωγή Λέξης στη Λίστα else p->next = insert(p->next, w); return p; } Αν είναι μεγαλύτερη, τότε πρόσθεσέ την πιο δεξιά στη λίστα, μετά από το τρέχον στοιχείο Κάλεσε αναδρομικά την insert Θέσε ως τρέχον στοιχείο, το επόμενο Επέστρεψε δείκτη στο νέο (ή παλιό) τρέχον στοιχείο Επειδή η λέξη μπορεί να προστεθεί αμέσως μετά την τρέχουσα, ο δείκτης στην επόμενη θέση της λίστας μπορεί να αλλάξει (μπορεί και όχι) Το τρέχον στοιχείο δείχνει στο στοιχείο της λίστας που επιστρέφει η insert
Τοποθέτηση Νέας Λέξης σε Ενδιάμεση Θέση της Λίστας ΔΕΝ αλλάζει ο δείκτης προς τη λίστα! Σημείο εισαγωγής Αρχική λίστα NULL Στοιχείο για εισαγωγή Τελική λίστα NULL
Αναδρομικός Ορισμός Επειδή ο ορισμός της insert είναι αναδρομικός, πρέπει να προηγείται η υπογραφή της συνάρτησης πριν τον ορισμό της struct node *insert(struct node *, char *);
Δέσμευση Μνήμης για Δημιουργία Κόμβου της Λίστας Συνάρτηση βιβλιοθήκης για ανάθεση μνήμης #include <stdlib.h> struct node *alloc(void) { return (struct node *) malloc(sizeof(struct node)); } Μέγεθος μνήμης ενός κόμβου casting
Δέσμευση μνήμης για το string Αντιγραφή Λέξης char *strdup(char *s) { char *p; p = (char *) malloc(strlen(s)+1); if (p != NULL) strcpy(p, s); return p; } Υπάρχει στη βιβλιο-θήκη string.h Δέσμευση μνήμης για το string Αντιγραφή string σε ασφαλές μέρος στη μνήμη
Δηλώσεις include & define #include <stdio.h> #include <string.h> #include <stdlib.h> #include <conio.h> #define MAXWORD 100
Εναλλακτικοί Ορισμοί Συναρτήσεων – Μη Αναδρομικοί void printlist (struct node *list) { while (list != NULL) { printf("%4d %s\n", list->count, list->word); list = list->next; } Βρόχος για όλα τα στοιχεία της λίστας Εκτύπωση των δεδομένων του τρέχοντος στοιχείου Μετακίνηση στο επόμενο στοιχείο της λίστας
Εναλλακτικοί Ορισμοί Συναρτήσεων – Μη Αναδρομικοί struct node * insert (struct node *p, char *w) { struct node *n,*r,*prev; int cond; r = p; if (p == NULL) { n = alloc(); n->word = strdup(w); n->count = 1; n->next = NULL; r = n; } else if (strcmp(w,p->word) < 0) { n = alloc(); n->word = strdup(w); n->count = 1; n->next = p; Εκτός αν το νέο στοιχείο προστεθεί στην αρχή ή αν η λίστα είναι άδεια Ο επιστρεφόμενος δείκτης r Συνήθως είναι ο αρχικός Άδεια λίστα Προσθήκη στοιχείου στην αρχή της λίστας
Εναλλακτικοί Ορισμοί Συναρτήσεων – Μη Αναδρομικοί else { while (p != NULL) { if ((cond = strcmp(w,p->word)) == 0) { p->count++; break; } else if (cond < 0) { n = alloc(); n->word=strdup(w); n->count = 1; n->next = p; prev->next = n; else if (cond > 0) { prev = p; p = p->next; Η λέξη συμπίπτει με την τρέχουσα Η λέξη είναι μικρότερη από την τρέχουσα Το προηγούμενο στοιχείο πρέπει να δείχνει στο νέο στοιχείο Το νέο στοιχείο προστίθεται στη μέση της λίστας Η λέξη είναι μεγαλύτερη από την τρέχουσα Προηγούμενο στοιχείο γίνεται το τρέχον Τρέχον στοιχείο γίνεται το επόμενο
Εναλλακτικοί Ορισμοί Συναρτήσεων – Μη Αναδρομικοί if (p == NULL) { n = alloc(); n->word=strdup(w); n->count = 1; n->next = NULL; prev->next = n; } return r; Το νέο στοιχείο προστίθεται στο τέλος της λίστας Το προηγούμενο στοιχείο πρέπει να δείχνει στο νέο στοιχείο Επιστροφή του πρώτου στοιχείου της λίστας Παλιού ή νέου
Συμπέρασμα Όταν έχουμε αυτό-αναφορικές ή αναδρομικές δομές δεδομένων οι αναδρομικές συναρτήσεις χειρισμού τους είναι πιο σύντομες και πιο ευκολονόητες από τις επαναληπτικές διαδικασίες
Εκτέλεση του Προγράμματος The function addtree is recursive. A word is ... parent node. 1 A 1 At 1 Eventually 1 If 1 The 5 a 11 the 4 word Εισαγωγή κειμένου από τον χρήστη Εκτύπωση αποτελεσμάτων από το πρόγραμμα
Απόδοση Καλύτερη περίπτωση αν οι λέξεις φθάνουν με φθίνουσα σειρά Κάθε φορά εξετάζεται μόνο η πρώτη θέση της λίστας Χειρότερη: αν είναι ταξινομημένες ήδη σε αύξουσα σειρά Κάθε φορά εξετάζονται όλες οι θέσεις της λίστας Όταν είναι τυχαία κατανεμημένες, η πολυπλοκότητα είναι ανάλογη του Ν2 Ν = ο αριθμός λέξεων
Δυαδικά Δένδρα Ένα δένδρο αποτελείται από στοιχεία που ονομάζονται κόμβοι (node) Οι κόμβοι είναι οργανωμένοι ιεραρχικά Ο κόμβος στην κορυφή της ιεραρχίας ονομάζεται ρίζα (root)
Δυαδικά Δένδρα Οι κόμβοι που βρίσκονται κάτω από έναν άλλο κόμβο είναι τα παιδιά του (children) Συνήθως τα παιδιά έχουν δικά τους παιδιά, κ.ο.κ. Κάθε κόμβος έχει ακριβώς έναν γονέα, εκτός από την ρίζα
Δυαδικά Δένδρα Ο αριθμός των παιδιών που μπο-ρεί να έχει ένας κόμβος εξαρτάται από το είδος του δένδρου Παράγοντας διακλάδωσης (branching factor) Θα δούμε τα δυαδικά δένδρα (binary tree) Παράγοντας διακλάδωσης = 2
Δυαδικά Δένδρα Δείκτης στη ρίζα του δένδρου Ρίζα Παιδιά Δεδομένα Δείκτης δεξιού υποδένδρου Δείκτης αριστερού υποδένδρου NULL Τερματικός κόμβος NULL NULL NULL NULL NULL NULL Δεδομένα Τερματισμός δένδρου
Δομή (struct) Δυαδικού Δένδρου Κάθε κόμβος του δυαδικού δένδρου είναι μία δομή με: Ένα ή περισσότερα μέλη για τα δεδομένα του κόμβου 1 δείκτη για το αριστερό παιδί (υποδένδρο) 1 δείκτη για το δεξί παιδί (υποδένδρο) struct tnode { int data; ... struct tnode *left; struct tnode *right; };
Χρήση Δένδρου για τη Μέτρηση Συχνότητας Εμφάνισης Λέξεων σε Κείμενο Σε κάθε κόμβο του δένδρου αποθη-κεύεται μία λέξη και ένας μετρητής struct tnode { char *word; int count; struct tnode *left; struct tnode *right; };
Χρήση Δένδρου για τη Μέτρηση Συχνότητας Εμφάνισης Λέξεων σε Κείμενο Η λέξεις τοποθετούνται στο δένδρο έτσι ώστε σε κάθε κόμβο: Όλες οι λέξεις που βρίσκονται στο αριστερό υποδένδρο να είναι λεξικογραφικά μικρότερες από τη λέξη του κόμβου Όλες οι λέξεις που βρίσκονται στο δεξί υποδένδρο να είναι λεξικογραφικά μεγαλύτερες από τη λέξη του κόμβου
Χρήση Δένδρου για τη Μέτρηση Συχνότητας Εμφάνισης Λέξεων σε Κείμενο Όταν το πρόγραμμα συναντάει μία νέα λέξη την τοποθετεί στο «σωστό» σημείο Ξεκινάει από τη ρίζα: Αν η νέα λέξη είναι μικρότερη από την αποθηκευμένη την ψάχνει στο αριστερό υποδένδρο Αν η νέα λέξη είναι μεγαλύτερη, την ψάχνει στο δεξί υποδένδρο Αν η νέα λέξη είναι ίδια, αυξάνει τον μετρητή κατά 1
Χρήση Δένδρου για τη Μέτρηση Συχνότητας Εμφάνισης Λέξεων σε Κείμενο Αν το δένδρο τερματιστεί (NULL), η λέξη δεν υπάρχει Δημιουργείται ένας νέος (τερματικός) κόμβος στον οποίο αποθηκεύεται η νέα λέξη (με μετρητή 1)
Παράδειγμα Δυαδικού Δένδρου Τοποθέτηση στο δένδρο των λέξεων της φράσης «now is the time for all good men to come to the aid of their party»
Κύριο Πρόγραμμα (main) void main () { struct tnode *root = NULL; char word[MAXWORD]; while (getword(word,MAXWORD)!=EOF) if (isalpha(word[0])) root = addtree(root, word); treeprint(root); } Κάθε λέξη τοποθετείται στη σωστή θέση Τυπώνεται το δένδρο (ταξινομημένο)
Εκτύπωση Κόμβων Δένδρου void treeprint(struct tnode *p) { if (p != NULL) { treeprint(p->left); printf("%4d %s\n", p->count, p->word); treeprint(p->right); } Εκτύπωση των κόμ-βων του αριστερού υποδένδρου Αναδρομική κλήση Τερματισμός αναδρομής Εκτύπωση των δεδομένων του τρέχοντος κόμβου Εκτύπωση των κόμβων του δεξιού υποδένδρου
Εισαγωγή Λέξης στο Δένδρο struct tnode *addtree(struct tnode *p, char *w) { int cond; if (p == NULL) { p = alloc(); p->word=strdup(w); p->count = 1; p->left = p->right = NULL; } Αν ο δείκτης δείχνει σε NULL δείκτη Φτάσαμε στο τέλος του δένδρου και η λέξη δεν βρέθηκε Πρέπει να προστεθεί νέος τερματικός κόμβος στο δένδρο Δέσμευση μνήμης Αποθήκευση λέξης Αποθήκευση μετρητή 1 Αφού ο νέος κόμβος είναι τερματικός, τα δυο παιδιά του είναι NULL
Αλλάζει ο δείκτης προς το δένδρο! Εισαγωγή Λέξης σε Άδειο Δένδρο Αρχικό Δένδρο NULL Αλλάζει ο δείκτης προς το δένδρο! Τελικό Δένδρο NULL NULL
Εισαγωγή Λέξης στο Δένδρο else if ((cond=strcmp(w, p->word)) == 0) p->count++; else if (cond < 0) p->left = addtree(p->left, w); else p->right = addtree(p->right, w); return p; } Αν είναι μικρότερη, τότε ψάξ’ την (αναδρομικά) στο αριστερό υποδένδρο Σύγκρινε την λέξη προς εισαγωγή με την αποθηκευμένη λέξη Αν είναι ίδιες, τότε αύξησε τον μετρητή κατά 1 Επειδή κάποιο υποδένδρο μπορεί να είναι NULL, η αναδρομή μπορεί να επιστρέψει δείκτη στο νέο κόμβο που θα προστεθεί Γι’ αυτό γίνεται ανάθεση τιμών στους δείκτες των υποδένδρων Αν είναι μεγαλύτερη, τότε ψάξ’ την (αναδρομικά) στο δεξί υποδένδρο
Εισαγωγή Λέξης στο Δένδρο Νέος Κόμβος Αρχικό Δένδρο NULL NULL NULL NULL Σημείο εισαγωγής NULL NULL NULL NULL
Εισαγωγή Λέξης στο Δένδρο Αλλάζει ο δείκτης στο σημείο εισαγωγής του κόμβου! ΔΕΝ αλλάζει ο δείκτης προς το δένδρο! Τελικό Δένδρο NULL NULL NULL NULL NULL NULL NULL
Αναδρομικός Ορισμός Επειδή ο ορισμός της addtree είναι αναδρομικός, πρέπει να προηγείται η υπογραφή της συνάρτησης πριν τον ορισμό της struct tnode *addtree(struct tnode *, char *);
Απόδοση Χειρότερη περίπτωση αν οι λέξεις είναι ταξινομημένες ήδη σε αύξουσα ή φθίνουσα σειρά Το δένδρο «γέρνει» μόνο από την δεξιά ή την αριστερή πλευρά, αντίστοιχα Καλύτερη περίπτωση αν οι λέξεις είναι τυχαία κατανεμημένες Το δένδρο είναι ισορροπημένο Σε κάθε σύγκριση αποκλείονται μισά στοιχεία του δένδρου Η πολυπλοκότητα είναι ανάλογη του Ν*log2Ν
Στοίβα (stack) Δομές δεδομένων για την αποθήκευση και την ανάκτηση δεδομένων με τη μέθοδο LIFO Last-In, First-Out Τα δεδομένα ανακτώνται με την ανάστροφή σειρά από αυτήν που αποθηκεύτηκαν Παράδειγμα: Στοίβα πιάτων
Λειτουργίες Στοίβας (Push) Νέα Κορυφή Στοίβας Νέο στοιχείο 3 Κορυφή Στοίβας Εισαγωγή Στοιχείου “PUSH” 3 7 7 5 5 Αρχική Στοίβα Τελική Στοίβα
Λειτουργίες Στοίβας (Pop) Κορυφή Στοίβας Ανακτημένο στοιχείο Νέα Κορυφή Στοίβας 3 Εξαγωγή Στοιχείου “POP” 3 7 7 5 5 Τελική Στοίβα Αρχική Στοίβα
Υλοποίηση Στοίβας ως Απλά Συνδεδεμένης Λίστας – Δομές/Επικεφαλίδες #include <stdio.h> #include <stdlib.h> struct node { int data; struct node *next; }; Διαφέρει ανάλογα με την εφαρμογή Απλά συνδεδεμένη λίστα
Συνάρτηση push void push (struct node **stack, int d) { Στοιχείο προς αποθήκευση void push (struct node **stack, int d) { struct node *p; p = alloc(); p->data = d; p->next = *stack; *stack = p; } Δείκτης σε λίστα, ώστε να μπορεί η συνάρτηση να αλλάζει το πρώτο στοιχείο της λίστας, δηλαδή την κορυφή της στοίβας Ο νέος κόμβος δείχνει στην παλιά κορυφή της στοίβας, αφού τοποθετείται μπροστά της Δημιουργία νέου κόμβου Ο δείκτης στο πρώτο στοιχείο της λίστας (στοίβας) «δείχνει» στο νέο κόμβο
Συνάρτηση pop int pop(struct node **stack, int *x) { struct node *p; Δείκτης σε ακέραιο Εξαχθέν στοιχείο Συνάρτηση pop Δείκτης σε λίστα int pop(struct node **stack, int *x) { struct node *p; p = *stack; if (*stack == NULL) return -1; else { *x = (*stack)->data; *stack = (*stack)->next; free(p); return 0; } Εξάγεται το στοιχείο της κορυφής της στοίβας (μέσω του δείκτη σε ακέραιο) Αντιγράφεται ο δείκτης στην κορυφή της στοίβας Αν η στοίβα είναι άδεια, επιστρέφεται ένδειξη «λάθους» Επιστρέφει ακέραιο για έλεγχο ορθής λειτουργίας Απελευθερώνεται η μνήμη που καταλάμβανε η παλιά κορυφή της στοίβας Η νέα κορυφή της στοίβας είναι πλέον το επόμενο στοιχείο της παλιάς κορυφής
Κυρίως Πρόγραμμα main void main () { struct node *stack = NULL; int x; while (scanf("%d",&x) != EOF) push(&stack,x); printf("\n\nInitial Stack:\n"); printstack(stack); if (pop(&stack,&x) != -1) { printf("\n\nElement popped: %d\n",x); printf("\nStack after pop:\n"); } else printf("Stack Empty!\n"); } Αρχικά η στοίβα είναι κενή Διαβάζονται ακέραιοι και αποθηκεύονται στη στοίβα Εξαγωγή ενός στοιχείου από τη στοίβα Εκτύπωση στοιχείων στοίβας Εκτύπωση του εξαχθέ-ντος στοιχείου και της υπόλοιπης στοίβας
Εκτέλεση του προγράμματος Initial Stack: 4 3 2 1 1 2 3 4 ^Z Element popped: 4 Stack after pop: 3 2 1 Είσοδος από το πληκτρολόγιο Έξοδος του προγράμματος
Ουρά (queue) Δομές δεδομένων για την αποθήκευση και την ανάκτηση δεδομένων με τη μέθοδο FIFO First-In, First-Out Τα δεδομένα ανακτώνται με την ίδια σειρά από αυτήν που αποθηκεύτηκαν Παράδειγμα: Ουρά εξυπηρέτησης σε ταμείο τράπεζας
Λειτουργίες Ουράς (Enqueue) Κεφαλή (head) Ουράς Κεφαλή (head) Ουράς «Ουρά» (tail) Ουράς 7 5 Αρχική Ουρά 7 Εισαγωγή Στοιχείου “ENQUEUE” 5 Τελική Ουρά 3 3 Νέο στοιχείο Νέα «Ουρά» (tail) Ουράς
Λειτουργίες Ουράς (Dequeue) Head Ουράς Ανακτημένο στοιχείο Νέο Head Ουράς 7 Εξαγωγή Στοιχείου “DEQUEUE” 7 Αρχική Ουρά 5 5 3 Τελική Ουρά 3 Tail Ουράς Tail Ουράς
Υλοποίηση Ουράς ως Απλά Συνδεδεμένης Λίστας – Δομές/Επικεφαλίδες #include <stdio.h> #include <stdlib.h> struct node { int data; struct node *next; }; struct queue { struct node *head; struct node *tail; Ίδια με το stack Δομή ουράς Έχει 2 δείκτες σε head και tail
Συνάρτηση enqueue void enqueue(struct queue *q, int d) Στοιχείο προς αποθήκευση void enqueue(struct queue *q, int d) { struct node *p; p = alloc(); p->data = d; p->next = NULL; if (q->tail == NULL) q->head = p; else q->tail->next = p; q->tail = p; } Δείκτης σε ουρά, ώστε να μπορεί η συνάρτηση να αλλάζει το head και το tail Δημιουργία νέου κόμβου Αν η ουρά είναι άδεια, τότε το head δείχνει στο νέο κόμβο Αν η ουρά δεν είναι άδεια, τότε το παλιό tail δείχνει στο νέο κόμβο, ο οποίος θα γίνει το νέο tail
Συνάρτηση dequeue Μοιάζει πολύ με το pop της στοίβας int dequeue(struct queue *q, int *x) { struct node *p; p = q->head; if (q->head == NULL) return -1; else { *x = q->head->data; q->head = q->head->next; free(p); q->tail = NULL; return 0; } Μοιάζει πολύ με το pop της στοίβας Αντί *stack έχουμε q->head Αν η ουρά αδειάσει, τότε ενημερώνεται και ο δείκτης στο tail
Συνάρτηση printqueue void printqueue (struct queue q) { struct node *h; h = q.head; while (h != NULL) { printf ("%4d\n", h->data); h = h->next; } Χρησιμοποιούμε τον επαναληπτικό τρόπο διάσχισης της λίστας (αντί αναδρομής) Η δομή queue δεν είναι αυτό-αναφορική
Κυρίως Πρόγραμμα main Η main μοιάζει πάρα πολύ με την main της στοίβας int main () { struct queue q = {NULL,NULL}; int x; while (scanf("%d",&x) != EOF) enqueue(&q,x); printf("\n\nInitial Queue:\n"); printqueue(q); if (dequeue(&q,&x) != -1) { printf("\n\nElement dequeued: %d\n",x); printf("\nQueue after dequeue:\n"); } else printf("Queue Empty!\n"); return 0; Η main μοιάζει πάρα πολύ με την main της στοίβας
Εκτέλεση του προγράμματος Initial Queue: 1 2 3 4 1 2 3 4 ^Z Element dequeued: 1 Queue after dequeue: 2 3 4 Είσοδος από το πληκτρολόγιο Έξοδος του προγράμματος