ΕΠΛ 231 ΔΟΜΕΣ ΔΕΔΟΜΕΝΩΝ ΚΑΙ ΑΛΓΟΡΙΘΜΟΙ 10/02/10 Παύλος Αντωνίου Στοίβες με Δυναμική Δέσμευση Μνήμης Στοίβα: Στοίβα είναι μια λίστα που έχει ένα επιπλέον περιορισμό. Ο περιορισμός είναι ότι οι εισαγωγές και οι εξαγωγές γίνονται μόνο από ένα σημείο, την κορυφή της στοίβας. Οι στοίβες είναι δομές δεδομένων οι οποίες υλοποιούν το μοντέλο LIFO: Last In First Out, δηλαδή το τελευταίο στοιχείο που έβαλα μέσα στη λίστα είναι αυτό που αφαιρώ πρώτο. Η λειτουργία της στοίβας μπορεί να απεικονιστεί σαν μια στοίβα από πιάτα σε μια καφετέρια, όπως φαίνεται στην Εικόνα 1. Τα φρεσκοπλυμένα πιάτα τοποθετούνται στην κεφαλή της στοίβας, κατά ανάλογο τρόπο με τη τοποθέτηση κόμβων στην στοίβα δεδομένων. Κάθε άτομο που στέκεται στην σειρά αναμονής, παίρνει ένα πιάτο. Αυτό ισοδυναμεί με την απομάκρυνση (αφαίρεση) δεδομένων. Εικόνα 1. Η κατάσταση αυτή είναι όμοια με την οργάνωση της δομής δεδομένων στοίβας, επειδή επιτρέπεται να τοποθετούμε πιάτα μόνο στην κεφαλή της στοίβας και να αφαιρούμε πιάτα πάλι μόνο από την κεφαλή της στοίβας. Δεν μπορούμε να προσπελάσουμε τα πιάτα που βρίσκονται στη μέση της στοίβας χωρίς να απομακρύνουμε τα πιάτα που βρίσκονται στην κεφαλή. Με όμοιο τρόπο δεν μπορούμε να προσπελάσουμε τους κόμβους που βρίσκονται στη μέση της στοίβας χωρίς να απομακρύνουμε τους κόμβους που βρίσκονται στην κεφαλή. Γιατί δημιουργούμε στοίβες: Πέραν από την ομοιότητα τους με δραστηριότητες της καθημερινής ζωής οι στοίβες μας επιτρέπουν να δημιουργούμε πιο περίπλοκες δομές δεδομένων όπως είναι τα δέντρα. Η υλοποίηση της στοίβας μπορεί να γίνει είτε με χρήση πινάκων (στατική μνήμη), είτε με χρήση δομής συνδεδεμένης λίστας (δυναμική μνήμη).
Υλοποίηση στοίβας με Δυναμική χορήγηση μνήμης, ως συνδεδεμένη λίστα Για να φτιάξουμε μια στοίβα πρέπει να δημιουργήσουμε μια λίστα η οποία θα συμπεριφέρεται σύμφωνα με το μοντέλο LIFO. Η λίστα γνωρίζουμε ότι αποτελείτε από κόμβους. Έστω ότι έχω μια συνδεδεμένη λίστα που κρατάει μια λίστα από αριθμούς τύπου int, όπως για παράδειγμα η λίστα στην εικόνα 2. Εικόνα 2 Η λίστα αποτελείται από κόμβους. Κάθε κόμβος είναι μια δομή, ένα struct. To struct του κόμβου μιας συνδεδεμένης λίστας μπορεί να έχει οποιοδήποτε αριθμό μελών, αλλά ένα μέλος θα πρέπει να είναι δείκτης για δομές ίδιου τύπου. Για παράδειγμα, για να ορίσω τη δομή του κόμβου μιας λίστας όμοιας με τη λίστα της εικόνας 2 γράφω τον ακόλουθο ορισμό: typedef struct node { int data; struct node *next; }NODE; Με την πιο πάνω δήλωση όρισα τη δομή του κόμβου της λίστας και έδωσα και το όνομα NODE σε αυτή τη δομή. Ο κόμβος που όρισα έχει την ακόλουθη μορφή: Ακόμα όμως δεν έχω αρχικοποιήσει καμία τιμή, έτσι δεν υπάρχει τιμή για το data που είναι τύπου int αλλά ούτε και ο δείκτης next δείχνει κάπου.
Εμείς στην προκειμένη περίπτωση θέλουμε να φτιάξουμε δομή στοίβας. Η στοίβα έχει την μορφή της εικόνας 3. Έστω ότι η στοίβα περιέχει τους αριθμούς 8,5,10 ήδη. Δηλαδή όπως φάινεται στην εικόνα, έχουμε βάλει πρώτα τον αριθμό 10, μετά τον αριθμό 5 και τελευταίο βάλαμε τον αριθμό 8. Μετά τον ορισμό του κόμβου ορίζω την ίδια τη στοίβα. Χρειάζομαι κάποια επιπλέον πεδία που θα με βοηθήσουν στο να υλοποιήσω μετά τις πράξεις πάνω σε κάθε κόμβο σύμφωνα με τo μοντέλο LIFO. Για αυτό το λόγο, δηλώνουμε στη δομή της στοίβας τα πεδία int size που είναι ένας αριθμός που θα κρατά το μέγεθος της στοίβας και ένα dummy κόμβο με το όνομα head στον οποίο δεν θα βάλουμε δεδομένα αλλά θα τον χρησιμοποιούμε πάντα σαν δείκτη προς το κόμβο που βρίσκεται στην κορυφή της στοίβας για να μπορoύμε να κάνουμε προσθαφαιρέσεις κόμβων. typedef struct stack{ NODE *head; int size; }STACK; Άρα, ο ορισμός του αφηρημένου τύπου δεδομένων Στοίβα είναι ο ακόλουθος: typedef struct node { int data; struct node *next; }NODE; typedef struct stack{ NODE *head; int size; }STACK; Για να ολοκληρωθεί ο ορισμός ενός τύπου δεδομένων, δεν αρκεί μόνο η δήλωση του αλλά πάντα χρειάζεται και η υλοποίηση των βασικών πράξεων του τύπου. Βασικές πράξεις στοίβας Πως ξεκινάμε τη δημιουργία μιας στοίβας; Μετά τους 2 πιο πάνω βασικούς ορισμούς δημιουργούμε κατ' αρχήν τον κόμβο της κεφαλής. head= struct (NODE*) malloc (sizeof(node));
Η στοίβα είναι άδεια προς το παρόν. Το μόνο που έχω είναι το κόμβο head που δεν δείχνει πουθενά γιατί ακόμα δεν υπάρχει κάτι στη στοίβα, δεν υπάρχει στοιχείο στη κεφαλή της και επίσης δεν έχει κάποια αρχική τιμή για το πεδίο data. Μπορώ να αρχικοποιήσω τη διεύθυνση του head να δείχνει στο null. Πως προσθέτουμε ένα στοιχείο στη στοίβα; Προσθέτουμε στοιχείο στη στοίβα με τη συνάρτηση Push (x, S) void push(int value, STACK *stack) { NODE *node; // allocate space node = (NODE *) malloc (sizeof(node)); // assign values node->data = value; node->next = stack->head; // point head to this node stack->head = node; } // increase stack size (stack->size)++; Εκτέλεση push (10, S) NODE *node; // allocate space node = (NODE *) malloc (sizeof(node)); Πρώτα δημιουργώ ένα νέο κόμβο τύπου NODE και του δίνω το όνομα node και στη συνέχεια δεσμεύω χώρο από τη μνήμη για το κόμβο (τύπου ΝΟDE). Στη συνέχεια δίνω τις τιμές στις μεταβλητές: node->data=10; node->next= stack->head; Το stack->head προηγουμένως αφού η λίστα μου ήταν κενή δεν έδειχνε πουθενά, ο δείκτης του ήταν NULL. Ουσιαστικά με την εντολή αυτή βάζω το νέο κόμβο να δείχνει εκεί που πριν έδειχνε ο
κόμβος head. Πριν την εντολή: Μετά την εντολή O κόμβος node δείχνει εκεί που έδειχνε ο κόμβος head. // point head to this node stack->head = node; Στη συνέχεια κάνω update το δείκτη stack-> head να δείχνει στον κόμβο που έχω βάλει τώρα γιατί αυτός ο κόμβος είναι ο κόμβος που τώρα είναι στην κορυφή της στοίβας. // increase stack size (stack->size)++; } Tέλος α υξάνω το μέγεθος της στοίβας κατά ένα. Εξάσκηση: προσθέστε με τη μέθοδο Push τους κόμβους 5 και 8 για να φτιάξετε τη στοίβα της εικόνας 3. Πως αφαιρώ κόμβο από τη στοίβα; Αφαιρούμε κόμβο με τη μέθοδο Pop(S). Η μέθοδος Pop(S) θα αφαιρέσει το στοιχείο που βρίσκεται στη κορυφή της στοίβας.
Έστω ότι βάλαμε τα στοιχεία 10, 8, 5 στη στοίβα και έχουμε την ακόλουθη μορφή: Εικόνα 3 Θα εκτελέσουμε τη μέθοδο Pop(S) για αυτή τη στοίβα για να αφαιρεθεί το στοιχείο της κεφαλής, δηλαδή ο κόμβος με τον αριθμό 8. void pop(stack *stack) { p = stack->head; if (IsEmptyStack( stack)) return; printf("%d\n", (stack-> head)->val); p = stack->head; stack->head = (stack->head)->next; (stack->size)--; } free(p); pop(s) p = stack->head; Αποθηκεύω το κόμβο (διεύθυνση) stack->head σε ένα κόμβο p.
if (IsEmptyStack(stack)) return; Ελέγχουμε αν είναι κενή η λίστα. Αν είναι κενή βγαίνουμε από τη μέθοδο. stack->head = (stack->head)->next; Ο δείκτης της κεφαλής τώρα δείχνει στο στοιχείο που βρισκόταν αν ακολουθούσαμε τη διεύθυνση next από τον κόμβο που έδειχνε προηγουμένως to head. Δηλαδή πριν έδειχνε στον κόμβο με το 8. Το next του κόμβου αυτού έδειχνε στον κόμβο με τον αριθμό 5. Άρα αφού θα αφαιρέσω το κόμβο με τον αριθμό 8, βάζω το δείκτη του head να δείχνει στο αμέσως επόμενο στοιχείο του 8, το οποίο είναι ο κόμβος με τον αριθμό 5. Έτσι, κανένας δείκτης δεν υπάρχει στο κόμβο με τον αριθμό 8 και ουσιαστικά ο χώρος μνήμης του κόμβου με τον αριθμό 8 είναι πάλι διαθέσιμος για οποιεσδήποτε άλλες πράξεις και δεν ανήκει ποια στη στοίβα μας.
Εκτελούμε free(p) γιατί δε χρειαζόμαστε άλλο το κόμβο p.