Δομές Δεδομένων Ενότητα 7: Μονές συνδεδεμένες λίστες Δρ. Γεώργιος Σίσιας Τμήμα Μηχανικών Πληροφορικής ΤΕ
Άδειες Χρήσης Το παρόν εκπαιδευτικό υλικό υπόκειται σε άδειες χρήσης Creative Commons. Για εκπαιδευτικό υλικό, όπως εικόνες, που υπόκειται σε άλλου τύπου άδειας χρήσης, η άδεια χρήσης αναφέρεται ρητώς. 2
Χρηματοδότηση Το παρόν εκπαιδευτικό υλικό έχει αναπτυχθεί στα πλαίσια του εκπαιδευτικού έργου του διδάσκοντα. Το έργο «Ανοικτά Ακαδημαϊκά Μαθήματα στο TEI Δυτικής Μακεδονίας και στην Ανώτατη Εκκλησιαστική Ακαδημία Θεσσαλονίκης» έχει χρηματοδοτήσει μόνο τη αναδιαμόρφωση του εκπαιδευτικού υλικού. Το έργο υλοποιείται στο πλαίσιο του Επιχειρησιακού Προγράμματος «Εκπαίδευση και Δια Βίου Μάθηση» και συγχρηματοδοτείται από την Ευρωπαϊκή Ένωση (Ευρωπαϊκό Κοινωνικό Ταμείο) και από εθνικούς πόρους. 3
Σκοποί ενότητας Σε αυτή την ενότητα παρουσιάζονται οι μονές συνδεδεμένες λίστες. 4
Περιεχόμενα ενότητας (1/2) Μονές συνδεδεμένες λίστες. Αναζήτηση, διαγραφή και προσθήκη στοιχείων. Μία μονή συνδεδεμένη λίστα. Βασικές λειτουργίες. Δήλωση τύπων. Επισημάνσεις υλοποίησης. Ενδεικτική υλοποίηση. Εισαγωγή κόμβου. 5
Περιεχόμενα ενότητας (2/2) Εισαγωγή/διαγραφή κόμβου στην αρχή της λίστας. Αποδέσμευση όλων των κόμβων. Παράδειγμα. Ζητήματα. Ασκήσεις. Βιβλιογραφία. 6
Μονές συνδεδεμένες λίστες (1/7) Οι μονές συνδεδεμένες λίστες αποτελούν μια γενίκευση των δυναμικών στοιβών και ουρών. Στην προκειμένη περίπτωση η εισαγωγή και διαγραφή των στοιχείων μπορεί να γίνει σε οποιοδήποτε σημείο της λίστας. Έτσι μπορούμε να αποθηκεύουμε τα στοιχεία με ταξινομημένη σειρά (όπως αυτό μπορεί να ορίζεται, π.χ. αλφαβητικά, κατά σειρά μεγέθους, κλπ), επιτρέποντας ή όχι την ύπαρξη διπλοτύπων. 7
Μονές συνδεδεμένες λίστες (2/7) Αν από τη δημιουργία της λίστας και μετέπειτα είμαστε συνεπείς ως προς την ταξινομημένη προσθήκη κόμβων, τότε μπορούμε να είμαστε βέβαιοι ότι τα στοιχεία είναι ανά πάσα στιγμή ταξινομημένα. Αν παραβιαστεί αυτή η συνθήκη, τότε πρέπει να έχουμε υπόψη ότι η ταξινόμηση μιας λίστας είναι, γενικά, δαπανηρή (αλγόριθμος). 8
Μονές συνδεδεμένες λίστες (3/7) Στην υλοποίηση που παρουσιάζουμε παρακάτω χρησιμοποιούμε τη δυναμική αποθήκευση κόμβων, όπως ακριβώς κάναμε στις δυναμικές στοίβες και ουρές. Υπάρχουν και άλλες μορφές υλοποιήσεων, αλλά δε θα ασχοληθούμε με αυτές. Συνεπώς θα έχουμε μια κεφαλή στην αρχή της λίστας (δηλαδή στον πρώτο κόμβο της αλυσίδας) και μια ακολουθία κόμβων όπου ο κάθε ένας αποθηκεύει τα δεδομένα που θέλουμε και ένα δείκτη στον επόμενο κόμβο. 9
Μονές συνδεδεμένες λίστες (4/7) Δηλαδή, κάθε κόμβος έχει προηγούμενο και επόμενο, με εξαίρεση τον πρώτο κόμβο που δεν έχει προηγούμενο και τον τελευταίο που δεν έχει επόμενο (ο δείκτης του προς τον επόμενο είναι NULL). Όπως υποδηλώνει το όνομά τους, οι κόμβοι στις μονές συνδεδεμένες λίστες έχουν δείκτη μόνο προς τον επόμενο κόμβο. 10
Μονές συνδεδεμένες λίστες (5/7) Υπάρχει και μια εκδοχή των συνδεδεμένων λιστών όπου σε κάθε έναν κόμβο αποθηκεύουμε τόσο το δείκτη προς τον επόμενο, όσο και προς τον προηγούμενο. Αυτές οι λίστες ονομάζονται διπλά συνδεδεμένες λίστες, και θα ασχοληθούμε αργότερα μαζί τους. Σε κάθε περίπτωση έχουμε την ευχέρεια να τροποποιήσουμε τις λειτουργίες που υποστηρίζονται από την υλοποίησή μας ανάλογα με τις ανάγκες του εκάστοτε προβλήματος. 11
Μονές συνδεδεμένες λίστες (6/7) Μια εκδοχή των μονών συνδεδεμένων λιστών είναι οι μονές κυκλικές συνδεδεμένες λίστες, στις οποίες ο δείκτης του τελευταίου κόμβου δείχνει πίσω στον πρώτο (στον οποίο δείχνει και ο δείκτης της αρχής της λίστας. Μια άλλη υλοποίηση μπορεί να απαιτεί την προσάρτηση (προσθήκη στο τέλος) μιας λίστας σε μια άλλη. 12
Μονές συνδεδεμένες λίστες (7/7) Στην περίπτωση αυτή μπορεί να έχουμε το ενδεχόμενο απώλειας της ταξινόμησης. Μια άλλη λειτουργία μπορεί να είναι η συγχώνευση μιας λίστας με μια άλλη, από την οποία προκύπτει μια τρίτη που περιέχει όλα τα στοιχεία των δύο αρχικών, αλλά με ταξινομημένη σειρά (υπό την προϋπόθεση ότι και οι δύο αρχικές ήταν ταξινομημένες). Στην παρούσα ενότητα θα δούμε ένα λεπτομερές παράδειγμα συγχώνευσης μιας ταξινομημένης λίστας σε μια άλλη, χωρίς τη δέσμευση πρόσθετων κόμβων, αλλά με την απώλεια της δεύτερης. 13
Αναζήτηση, διαγραφή και προσθήκη στοιχείων (1/3) Όσον αφορά την αναζήτηση ή/και διαγραφή/προσθήκη στοιχείων συγκριτικά με τους πίνακες, ισχύουν τα παρακάτω: 1. Η αναζήτηση ενός κόμβου σε μια συνδεδεμένη λίστα απαιτεί τη σάρωση της λίστας από την αρχή της, μέχρι να βρεθεί ο επιθυμητός κόμβος. Στην περίπτωση ενός ταξινομημένου πίνακα η αναζήτηση είναι πολύ ευκολότερη (βλ. δυαδική αναζήτηση), ή και άμεση, αν γνωρίζουμε τη θέση του προς αναζήτηση στοιχείου στον πίνακα. 14
Αναζήτηση, διαγραφή και προσθήκη στοιχείων (2/3) 2. Η εισαγωγή ενός στοιχείου στη «σωστή» θέση του σε μια λίστα δεν απαιτεί τη μετατόπιση κόμβων. Μετά τον εντοπισμό της κατάλληλης τοποθεσίας εισαγωγής του νέου κόμβου, απλά πρέπει να αναδιατάξουμε τους δείκτες ώστε να το συνδέσουμε στη λίστα. Η αντίστοιχη ενέργεια σε έναν ταξινομημένο πίνακα απαιτεί όχι μόνο τον εντοπισμό (που μπορεί να είναι εύκολος), αλλά και τη δημιουργία χώρου (με τη μετατόπιση κατά μία θέση των υπολοίπων στοιχείων), ώστε να εισαχθεί το νέο στοιχείο στον πίνακα. 15
Αναζήτηση, διαγραφή και προσθήκη στοιχείων (3/3) 3. Η διαγραφή ενός στοιχείου από μια συνδεδεμένη λίστα είναι παρόμοια με την εισαγωγή: Εντοπισμός του επιθυμητού κόμβου, δημιουργία παράκαμψης ώστε να αφαιρεθεί από την αλυσίδα, και τέλος αποδέσμευση του χώρου του. Στην περίπτωση του ταξινομημένου πίνακα απαιτείται ο εντοπισμός του στοιχείου (με οποιονδήποτε τρόπο αυτό μπορεί να επιτευχθεί), και η μετατόπιση των μετέπειτα στοιχείων κατά μία θέση. 16
Μία μονή συνδεδεμένη λίστα (1/2) Συνεπώς, η επιλογή μίας γραμμικής (πίνακας) ή συνδεδεμένης υλοποίησης βασίζεται στο συσχετισμό των ενεργειών που πρέπει να εκτελεστούν. Στο παρακάτω σχήμα απεικονίζεται μια μονή συνδεδεμένη λίστα που έχει τέσσερις κόμβους με δεδομένα 10, 20, 30, και 40. Αν, για παράδειγμα, θέλαμε να προσθέσουμε το στοιχείο 25, αυτό θα γινόταν ανάμεσα στους κόμβους που περιέχουν τα 20 και 30. 17
Μία μονή συνδεδεμένη λίστα (2/2) Εικόνα 1. Μονή συνδεδεμένη λίστα. Πηγή: Διδάσκων (2015). 18
Βασικές λειτουργίες (1/2) Οι βασικές λειτουργίες μιας συνδεδεμένης λίστας είναι οι ακόλουθες: 1. Αρχικοποίηση (δηλωμένης) λίστας. 2. Έλεγχος για κενή λίστα. 3. Εισαγωγή στοιχείου σε μια λίστα. 4. Διαγραφή στοιχείου από μια λίστα. 5. Αποδέσμευση μιας λίστας. 19
Βασικές λειτουργίες (2/2) Όπως και στην περίπτωση και των άλλων δομών που έχουμε δει μέχρι τώρα, μπορούμε να έχουμε και πρόσθετες λειτουργίες (π.χ. την αντιστροφή μιας λίστας, τη συγχώνευση, το μέτρημα των κόμβων, κλπ). Ας αρχίσουμε με τη δήλωση των τύπων. 20
Δήλωση τύπων (1/3) typedef int TItem; typedef struct TSLListTAG { TItem Item; TSLListTAG *Next; } TSLList; 21
Δήλωση τύπων (2/3) Η πρώτη δήλωση αφορά το γενικό τύπο των στοιχείων που θα αποθηκεύουμε σε κάθε μία θέση της λίστας, που στην περίπτωσή μας θα είναι ένας ακέραιος. Η δήλωση αυτού του ενδιάμεσου τύπου μάς βοηθάει στην ελάττωση των αλλαγών στον κώδικα, όποτε αυτό καταστεί αναγκαίο. Η δεύτερη δήλωση είναι αυτή του κόμβου της λίστας. 22
Δήλωση τύπων (3/3) Ο τύπος του κόμβου της λίστας (TSLList) αποτελείται από τα εξής δύο κατά σειρά πεδία: 1. Τα δεδομένα που θα αποθηκεύσουμε σε κάθε έναν κόμβο. 2. Το δείκτη προς τον επόμενο κόμβο. 23
Επισημάνσεις υλοποίησης (1/6) 1. Ο κώδικας που δείχνουμε στην αρχή είναι αυτό που ο μεταγλωττιστής αποκαλεί forward declaration. Δηλαδή, στον κώδικα πριν την υλοποίηση των συναρτήσεων έχουμε παραθέσει τις δηλώσεις τους. Αυτό σημαίνει ότι το όνομα, η λίστα των παραμέτρων, και η τιμή που επιστρέφουν οι συναρτήσεις είναι γνωστές εκ των προτέρων, πριν ακόμη αυτές υλοποιηθούν. 24
Επισημάνσεις υλοποίησης (2/6) 1. (Συνέχεια). Αυτό μας βοηθάει στην περίπτωση που στον κώδικα μιας συνάρτησης καλούμε κάποια άλλη. Αυτό από μόνο του δεν αποτελεί απαραίτητα πρόβλημα, παρά μόνο στην περίπτωση που κάποιες συναρτήσεις εξαρτώνται από άλλες (δηλαδή τις χρησιμοποιούν), αλλά αυτές δεν μπορούν πάντοτε να προηγούνται στον κώδικα. 25
Επισημάνσεις υλοποίησης (3/6) 1. (Συνέχεια). Με άλλα λόγια, μια συνάρτηση δεν είναι απαραίτητο να είναι υλοποιημένη πριν από αυτήν που θα τη χρησιμοποιήσει, κάτι που μας βοηθάει στο να υλοποιήσουμε τις επιμέρους λειτουργίες με τη σειρά που εμείς επιθυμούμε, και όχι απαραίτητα με κάποια αναγκαία σειρά. 26
Επισημάνσεις υλοποίησης (4/6) 2. Ο κώδικας που δείχνουμε καλύπτει αρκετά περισσότερες λειτουργίες από αυτές που περιγράψαμε πιο πάνω. Πάρ' όλα αυτά, κάτι τέτοιο θα χρησιμεύσει στις ασκήσεις. Στην εξήγηση που θα δώσουμε θα περιοριστούμε στις βασικές λειτουργίες που αναφερθήκαμε πιο πάνω. 27
Επισημάνσεις υλοποίησης (5/6) 2. (Συνέχεια). Σε κάποιες περιπτώσεις η δήλωση και η υλοποίηση κάποιων λειτουργιών λείπουν σκόπιμα. Αφήνουμε τον αναγνώστη να προσπαθήσει να τις υλοποιήσει. Ακόμη, κάποιες λειτουργίες δεν είναι υλοποιημένες με το βέλτιστο ή πιο κομψό τρόπο. 28
Επισημάνσεις υλοποίησης (6/6) Ακολουθεί μια ενδεικτική αλλά και μακροσκελής υλοποίηση. Ο κώδικας του κυρίως προγράμματος εξετάζει την επί τόπου συγχώνευση δύο συνδεδεμένων λιστών - θέμα εξετάσεων του εργαστηρίου του 2012. 29
Ενδεικτική υλοποίηση (1/49) /* -------------------------------------------------------------------------- */ #include <stdio.h> #include <stdlib.h> /* -------------------------------------------------------------------------- */ typedef int TItem; typedef struct TSLListTAG { TItem TSLListTAG *Next; } TSLList; Item; 30
Ενδεικτική υλοποίηση (2/49) /* -------------------------------------------------------------------------- */ // Function declarations. void bool bool bool bool int int SLList_Init(TSLList **Head); SLList_IsEmpty(TSLList *Head); SLList_Insert(TSLList **Head, TItem Item); SLList_InsertUnique(TSLList **Head, TItem Item); SLList_RemoveFirst(TSLList **Head, TItem Item); SLList_RemoveAll(TSLList **Head, TItem Item); SLList_RemoveDuplicates(TSLList **Head); 31
Ενδεικτική υλοποίηση (3/49) //bool //bool SLList_Merge(TSLList *List1, TSLList *List2, TSLList **NewList); SLList_MergeUnique(TSLList *List1, TSLList *List2, TSLList **NewList) //bool SLList_Copy(TSLList **Dest, TSLList *Src) void SLList_MergeInPlace(TSLList **L1, TSLList **L2); bool SLList_Ascending(TSLList *Head); bool SLList_Descending(TSLList *Head); TSLList* SLList_FindFirst(TSLList *Head, TItem Item); int SLList_Count(TSLList *Head); int SLList_CountItem(TSLList *Head, TItem Item); void SLList_Deallocate(TSLList **Head); void SLList_Write(TSLList *Head); 32
Ενδεικτική υλοποίηση (4/49) void SLList_Init(TSLList **Head) { *Head = NULL; } // SLList_Init bool SLList_IsEmpty(TSLList *Head) { return ( Head == NULL ); } // SLList_IsEmpty 33
Ενδεικτική υλοποίηση (5/49) bool SLList_Insert(TSLList **Head, TItem Item) { TSLList *NewItem, *Curr, *Prev; // Allocate space for new node. NewItem = (TSLList *) malloc(sizeof(tsllist)); if ( NewItem == NULL ) return ( false ); NewItem->Item = Item; // Initialise traversal. Curr = *Head; Prev = NULL; 34
Ενδεικτική υλοποίηση (6/49) // Advance to the proper sorted position. while ( ( Curr!= NULL ) && ( Curr->Item < Item ) ) // What if <=? { Prev = Curr; Curr = Curr->Next; } // while 35
Ενδεικτική υλοποίηση (7/49) // Insert at the beginning of the list. if ( Prev == NULL ) { NewItem->Next = *Head; // or NewItem->Next = Curr; *Head = NewItem; } else // Insert between nodes or at the end of the list. { NewItem->Next = Curr; Prev->Next = NewItem; } // if return ( true ); } // SLList_Insert 36
Ενδεικτική υλοποίηση (8/49) bool SLList_InsertUnique(TSLList **Head, TItem Item) { TSLList *NewItem, *Curr, *Prev; // Allocate space for new node. NewItem = (TSLList *) malloc(sizeof(tsllist)); if ( NewItem == NULL ) return ( false ); NewItem->Item = Item; // Initialise traversal. Curr = *Head; Prev = NULL; 37
Ενδεικτική υλοποίηση (9/49) // Advance to the proper sorted position. while ( ( Curr!= NULL ) && ( Curr->Item < Item ) ) { Prev = Curr; Curr = Curr->Next; } // while // Don't add item on the list if it already exists. if ( ( Curr!= NULL ) && ( Item == Curr->Item ) ) return ( false ); 38
Ενδεικτική υλοποίηση (10/49) // Insert at the beginning of the list. if ( Prev == NULL ) { NewItem->Next = *Head; // or NewItem->Next = Curr; *Head = NewItem; } else // Insert between nodes or at the end of the list. { NewItem->Next = Curr; Prev->Next = NewItem; } // if return ( true ); } // SLList_InsertUnique 39
Ενδεικτική υλοποίηση (11/49) bool SLList_RemoveFirst(TSLList **Head, TItem Item) { // Initialise traversal. TSLList *Curr = *Head, *Prev = NULL; // Advance to the proper sorted position. while ( ( Curr!= NULL ) && ( Curr->Item < Item ) ) { Prev = Curr; Curr = Curr->Next; } // while 40
Ενδεικτική υλοποίηση (12/49) // If Item is present, delete it. if ( ( Curr!= NULL ) && ( Item == Curr->Item ) ) { // Remove from the beginning of the list. if ( Prev == NULL ) *Head = Curr->Next; else // or from further down. Prev->Next = Curr->Next; free(curr); return ( true ); } else { return ( false ); } // if } // SLList_RemoveFirst 41
Ενδεικτική υλοποίηση (13/49) int SLList_RemoveAll(TSLList **Head, TItem Item) { TSLList *Prev = NULL, *Curr = *Head, *Tmp; int Counter = 0; while ( Curr!= NULL ) { if ( Curr->Item!= Item ) { Prev = Curr; Curr = Curr->Next; } 42
Ενδεικτική υλοποίηση (14/49) else { } // if } // while return ( Counter ); } // SLList_RemoveAll // Found item to delete if ( Prev!= NULL ) // Item further down on the list Prev->Next = Curr->Next; else // First item on the list *Head = Curr->Next; Tmp = Curr; Curr = Curr->Next; free(tmp); Counter++; 43
Ενδεικτική υλοποίηση (15/49) int SLList_RemoveDuplicates(TSLList **Head) { TSLList *Prev = *Head, *Curr, *Tmp; int Counter = 0; if ( *Head == NULL ) return ( 0 ); Curr = (*Head)->Next; 44
Ενδεικτική υλοποίηση (16/49) while ( Curr!= NULL ) { if ( Prev->Item!= Curr->Item ) { Prev = Curr; Curr = Curr->Next; } else { Prev->Next = Curr->Next; Tmp = Curr; Curr = Curr->Next; free(tmp); Counter++; } // if } // while return ( Counter ); } // SLList_RemoveDuplicates 45
Ενδεικτική υλοποίηση (17/49) /* bool SLList_Merge(TSLList *List1, TSLList *List2, TSLList **NewList) { TSLList *Curr1 = List1, *Curr2 = List2, *CurrNew, *NewItem; bool OK = true; // Deallocate any existing items of the final list. SLList_Deallocate(NewList); CurrNew = *NewList; 46
Ενδεικτική υλοποίηση (18/49) // Merge the two lists. while ( ( ( Curr1!= NULL ) ( Curr2!= NULL ) ) && OK ) { // Attempt to allocate memory for one more item. NewItem = (TSLList *) malloc(sizeof(tsllist)); OK = ( NewItem!= NULL ); // If successful, proceed. if ( OK ) { NewItem->Next = NULL; 47
Ενδεικτική υλοποίηση (19/49) } if ( ( Curr1!= NULL ) && ( Curr2!= NULL ) ) { if ( Curr1->Item <= Curr2->Item ) { // Add Curr1->Item. if ( *NewList!= NULL ) { } else { } // if Curr1 = Curr1->Next; } else { // Add Curr2->Item. Curr2 = Curr2->Next; } // if 48
Ενδεικτική υλοποίηση (20/49) else if ( ( Curr1!= NULL ) && ( Curr2 == NULL ) ) { // Add Curr1->Item; Curr1 = Curr1->Next; } else if ( ( Curr1 == NULL ) && ( Curr2!= NULL ) ) { // Add Curr2->Item; Curr2 = Curr2->Next; } // if } // if } // while 49
Ενδεικτική υλοποίηση (21/49) // In case of memory allocation failure, deallocate final list. if (!OK ) SLList_Deallocate(NewList); // Report. return ( OK ); } // SLList_Merge */ /* bool SLList_MergeUnique(TSLList *List1, TSLList *List2, TSLList **NewList) { } // SLList_MergeUnique bool SLList_Copy(TSLList **Dest, TSLList *Src) { } // SLList_Copy */ 50
Ενδεικτική υλοποίηση (22/49) void SLList_MergeInPlace(TSLList **L1, TSLList **L2) { TSLList *Prev, *Curr, *Tmp; // Check if either list is empty. if ( *L1 == NULL ) // L1 is empty. { *L1 = *L2; // L1 should point to L2. *L2 = NULL; // Get rid of L2 now. } // if if ( *L2 == NULL ) return; // L2 is empty. // Prepare the traversal of the lists. Prev = NULL; // Used on L1 to insert nodes between Prev and Curr. Curr = *L1; // Used on L1 to check the order of data. Tmp = *L2; // Used on L2 to link safely the head pointer to next node. 51
Ενδεικτική υλοποίηση (23/49) // Merge in place. while ( ( Curr!= NULL ) && ( Tmp!= NULL ) ) { // Advance to the proper sorted position on L1. while ( ( Curr!= NULL ) && ( Curr->Item <= Tmp->Item ) ) // What if <? { Prev = Curr; Curr = Curr->Next; } // while 52
Ενδεικτική υλοποίηση (24/49) // Link nodes appropriately. *L2 = Tmp->Next; // (1) Remember the 2nd item of L2. if ( Prev == NULL ) // Insert as first item of L1... *L1 = Tmp; // (2) L2's first item will go to top of L1. else //... or further down on L1. Prev->Next = Tmp; // (2) L2's first item will go after Prev. Tmp->Next = Curr; // (3) Point to the next item of L1. Prev = Tmp; // (4) This is our new "Prev" item. Tmp = *L2; // (5) Pointing again the the 1st item of L2. } // while 53
Ενδεικτική υλοποίηση (25/49) //If L1 has run out of elements, append the remaining of L2 and the of it. //Otherwise if L2 has run out of elements, do nothing. if ( Curr == NULL ) { Prev->Next = *L2; *L2 = NULL; } // if } // SLList_MergeInPlace 54
Ενδεικτική υλοποίηση (26/49) bool SLList_Ascending(TSLList *Head) { TItem TSLList bool Item; *Tmp = Head; Sorted = true; if ( Tmp == NULL ) return ( true ); Item = Tmp->Item; Tmp = Tmp->Next; 55
Ενδεικτική υλοποίηση (27/49) while ( ( Tmp!= NULL ) && Sorted ) { Sorted = ( Tmp->Item >= Item ); Item = Tmp->Item; Tmp = Tmp->Next; } // while return ( Sorted ); } // SLList_Ascending 56
Ενδεικτική υλοποίηση (28/49) bool SLList_Descending(TSLList *Head) { TItem Item; TSLList *Tmp = Head; bool Sorted = true; if ( Tmp == NULL ) return ( true ); Item = Tmp->Item; Tmp = Tmp->Next; while ( ( Tmp!= NULL ) && Sorted ) { Sorted = ( Tmp->Item <= Item ); Item = Tmp->Item; Tmp = Tmp->Next; } // while return ( Sorted ); } // SLList_Descending 57
Ενδεικτική υλοποίηση (29/49) TSLList* SLList_FindFirst(TSLList *Head, TItem Item) { TSLList *Tmp = Head; while ( Tmp!= NULL ) { if ( Tmp->Item == Item ) return ( Tmp ); Tmp = Tmp->Next; } // while return ( Tmp ); // Or: return ( NULL ); } // SLList_FindFirst 58
Ενδεικτική υλοποίηση (30/49) int SLList_Count(TSLList *Head) { TSLList *Tmp = Head; int Counter = 0; while ( Tmp!= NULL ) { Counter++; Tmp = Tmp->Next; } // while return ( Counter ); } // SLList_Count 59
Ενδεικτική υλοποίηση (31/49) int SLList_CountItem(TSLList *Head, TItem Item) { TSLList *Tmp = Head; int Counter = 0; while ( Tmp!= NULL ) { if ( Tmp->Item == Item ) Counter++; Tmp = Tmp->Next; } // while return ( Counter ); } // SLList_CountItem 60
Ενδεικτική υλοποίηση (32/49) void SLList_Deallocate(TSLList **Head) { TSLList *Tmp = *Head; while ( *Head!= NULL ) { *Head = (*Head)->Next; free(tmp); Tmp = *Head; } // while } // SLList_Deallocate 61
Ενδεικτική υλοποίηση (33/49) void SLList_Write(TSLList *Head) { TSLList *Tmp = Head; printf("\nsingle-linked List\n"); printf("------------------\n"); printf(" Head Node = 0x%08X\n", Head); printf("item Count = %3d\n", SLList_Count(Head)); printf("list Items = Item (Next)\n"); while ( Tmp!= NULL ) { printf(" %4d (0x%08X)\n", Tmp->Item, Tmp->Next); Tmp = Tmp->Next; } // while printf("\n"); } // SLList_Write 62
Ενδεικτική υλοποίηση (34/49) void main() { /* TSLList *List; SLList_Init(&List); SLList_Write(List); SLList_Insert(&List, 10); SLList_Insert(&List, 20); SLList_Insert(&List, 30); SLList_Insert(&List, 40); SLList_Write(List); 63
Ενδεικτική υλοποίηση (35/49) SLList_InsertUnique(&List, 30); SLList_Write(List); SLList_RemoveFirst(&List, 30); SLList_Write(List); SLList_Insert(&List, 40); SLList_Write(List); SLList_Insert(&List, 50); SLList_Insert(&List, 15); SLList_Insert(&List, 12); SLList_Insert(&List, 5); SLList_Write(List); 64
Ενδεικτική υλοποίηση (36/49) printf("\n40 appears %d times\n", SLList_CountItem(List, 40)); printf("\naddress of first item : 0x%08X\n", SLList_FindFirst(List, 5)); printf("\n40 deleted %d times\n", SLList_RemoveAll(&List, 40)); SLList_Write(List); printf("\n5 deleted %d times\n", SLList_RemoveAll(&List, 5)); SLList_Write(List); 65
Ενδεικτική υλοποίηση (37/49) SLList_Insert(&List, 50); SLList_Insert(&List, 15); SLList_Insert(&List, 10); SLList_Write(List); SLList_RemoveDuplicates(&List); SLList_Write(List); SLList_Deallocate(&List); SLList_Write(List); */ TSLList *L2, *L1; 66
Ενδεικτική υλοποίηση (38/49) // Test 1: L1 has less items than L2. printf("===== TEST 1 =====\n"); SLList_Init(&L1); SLList_Init(&L2); printf("\n"); printf("construct L1\n"); printf("------------\n"); SLList_Insert(&L1, 20); SLList_Insert(&L1, 20); SLList_Insert(&L1, 40); SLList_Write(L1); 67
Ενδεικτική υλοποίηση (39/49) printf("\n"); printf("construct L2\n"); printf("------------\n"); SLList_Insert(&L2, 10); SLList_Insert(&L2, 30); SLList_Insert(&L2, 50); SLList_Insert(&L2, 60); SLList_Insert(&L2, 70); SLList_Write(L2); 68
Ενδεικτική υλοποίηση (40/49) printf("\n"); printf("merge L2 onto L1\n"); printf("----------------\n"); SLList_MergeInPlace(&L1, &L2); printf("\n"); printf("write L1\n"); printf("--------\n"); SLList_Write(L1); printf("\n"); printf("write L2\n"); printf("--------\n"); SLList_Write(L2); SLList_Deallocate(&L1); SLList_Deallocate(&L2); 69
Ενδεικτική υλοποίηση (41/49) // Test 2: L1 has more items than L2. printf("===== TEST 2 =====\n"); SLList_Init(&L1); SLList_Init(&L2); printf("\n"); printf("construct L1\n"); printf("------------\n"); SLList_Insert(&L1, 20); SLList_Insert(&L1, 20); SLList_Insert(&L1, 40); SLList_Insert(&L1, 60); SLList_Insert(&L1, 70); SLList_Write(L1); 70
Ενδεικτική υλοποίηση (42/49) printf("\n"); printf("construct L2\n"); printf("------------\n"); SLList_Insert(&L2, 10); SLList_Insert(&L2, 30); SLList_Insert(&L2, 50); SLList_Write(L2); 71
Ενδεικτική υλοποίηση (43/49) printf("\n"); printf("merge L2 onto L1\n"); printf("----------------\n"); SLList_MergeInPlace(&L1, &L2); printf("\n"); printf("write L1\n"); printf("--------\n"); SLList_Write(L1); printf("\n"); printf("write L2\n"); printf("--------\n"); SLList_Write(L2); 72
Ενδεικτική υλοποίηση (44/49) SLList_Deallocate(&L1); SLList_Deallocate(&L2); // Test 3: Both lists are empty. printf("===== TEST 3 =====\n"); SLList_MergeInPlace(&L1, &L2); SLList_Write(L1); SLList_Write(L2); 73
Ενδεικτική υλοποίηση (45/49) // Test 4: Both lists have the same number of nodes. printf("===== TEST 4 =====\n"); SLList_Insert(&L1, 10); SLList_Insert(&L1, 30); SLList_Insert(&L2, 20); SLList_Insert(&L2, 40); SLList_MergeInPlace(&L1, &L2); SLList_Write(L1); SLList_Write(L2); SLList_Deallocate(&L1); SLList_Deallocate(&L2); 74
Ενδεικτική υλοποίηση (46/49) // Test 5: L1 does not have any nodes at all. printf("===== TEST 5 =====\n"); // SLList_Insert(&L1, 10); // SLList_Insert(&L1, 30); SLList_Insert(&L2, 20); SLList_Insert(&L2, 40); SLList_MergeInPlace(&L1, &L2); SLList_Write(L1); SLList_Write(L2); SLList_Deallocate(&L1); SLList_Deallocate(&L2); 75
Ενδεικτική υλοποίηση (47/49) // Test 6: L2 does not have any nodes at all. printf("===== TEST 6 =====\n"); SLList_Insert(&L1, 10); SLList_Insert(&L1, 30); // SLList_Insert(&L2, 20); // SLList_Insert(&L2, 40); SLList_MergeInPlace(&L1, &L2); SLList_Write(L1); SLList_Write(L2); SLList_Deallocate(&L1); SLList_Deallocate(&L2); } // main 76
Ενδεικτική υλοποίηση (48/49) Η συνάρτηση void SLList_Init(TSLList **Head) αρχικοποιεί το δείκτη της κεφαλής μιας συνδεδεμένης λίστας στο NULL (δηλαδή, στην αρχή δεν υπάρχουν κόμβοι). Η συγκεκριμένη συνάρτηση δεν επιστρέφει τίποτα, αλλά μεταβάλλει την παράμετρο. Η συνάρτηση bool SLList_IsEmpty(TSLList *Head) ελέγχει αν μια λίστα περιέχει κόμβους ή είναι κενή. Η επιστρεφόμενη τιμή είναι true αν η λίστα είναι κενή (η κεφαλή είναι NULL) ή false αν περιέχει κόμβους (η κεφαλή ΔΕΝ είναι NULL). 77
Ενδεικτική υλοποίηση (49/49) Πριν περιγράψουμε την εισαγωγή και διαγραφή κόμβων από μια λίστα, είναι σκόπιμο να αναφέρουμε μια βασική (δηλ. διάσχιση μιας λίστας με σκοπό την εισαγωγή ή διαγραφή κόμβων) και μια δευτερεύουσα τεχνική. 78
Εισαγωγή κόμβου (1/4) Όταν σαρώνουμε μια λίστα με τη βοήθεια ενός δείκτη, ο δείκτης αυτός κάποια στιγμή θα «βγει εκτός της λίστας». Δηλαδή κάποια στιγμή, όταν φτάσουμε στο τέλος της, θα πάρει την τιμή NULL. Αν εμείς θέλαμε να προσθέσουμε έναν κόμβο στο τέλος της λίστας, τότε έχουμε ήδη χάσει το δείκτη στον τελευταίο κόμβο. 79
Εισαγωγή κόμβου (2/4) Το ίδιο ισχύει και όταν θέλουμε να προσθέσουμε έναν κόμβο ανάμεσα σε δύο άλλους. Τη στιγμή που θα έχουμε βρει τον κόμβο που έπεται λογικά αυτού προς εισαγωγή, τότε είναι πια αργά. Έτσι, κατά τη διάσχιση της λίστας χρειαζόμαστε δύο δείκτες: τον Prev και τον Curr. 80
Εισαγωγή κόμβου (3/4) Ο Curr δείχνει πάντοτε στο στοιχείο που μας ενδιαφέρει, ενώ το Prev στο αμέσως προηγούμενο. Συνεπώς, με τη βοήθεια του Prev μπορούμε να προβούμε στις απαραίτητες ενέργειες. Σε κάθε περίπτωση, είτε κατά τη σάρωση είτε μετά από μια εισαγωγή κόμβου, οι δύο αυτοί δείκτες πρέπει ΠΑΝΤΟΤΕ να δείχνουν σε δύο διαδοχικούς κόμβους. 81
Εισαγωγή κόμβου (4/4) Δηλαδή δεν πρέπει ΠΟΤΕ να παρεμβάλλεται κόμβος ανάμεσα σε αυτούς που δείχνει ο κάθε ένας δείκτης. Η αρχική τιμή του Prev είναι NULL, ενώ του Curr είναι ίδια με το δείκτη Head (ο οποίος μπορεί κάλλιστα να είναι NULL στην περίπτωση μιας κενής λίστας). 82
Διαγραφή κόμβου Εδώ ισχύει ακριβώς το ίδιο με πριν. Κατά τη διάσχιση της λίστας πρέπει να «θυμόμαστε» τον προηγούμενο κόμβο, αφού ο δείκτης Curr δείχνει στο στοιχείο που εξετάζουμε ανά πάσα στιγμή. Αφού η λίστα είναι μονή, δεν έχουμε δείκτη στον προηγούμενο κόμβο ώστε να παρακάμψουμε αυτόν που ψάχνουμε και ίσως θέλουμε να διαγράψουμε (πηγαίνοντας ξανά στον προηγούμενο κόμβο για λίγο). 83
Εισαγωγή/διαγραφή κόμβου στην αρχή της λίστας (1/9) Η εισαγωγή ή διαγραφή κόμβου στην αρχή της λίστας (δηλαδή αμέσως μετά την κεφαλή) διαφέρει από την ίδια ενέργεια πιο κάτω στη λίστα. Ο λόγος είναι ότι στην πρώτη περίπτωση πρέπει να μεταβάλλουμε το δείκτη της κεφαλής, ενώ στη δεύτερη το δείκτη Next του προηγούμενου κόμβου (Prev) από αυτόν στον οποίο βρισκόμαστε (Curr). 84
Εισαγωγή/διαγραφή κόμβου στην αρχή της λίστας (2/9) Δηλαδή, συνοψίζοντας, ο κώδικας για την εισαγωγή/διαγραφή ανάμεσα σε δύο κόμβους είναι ο ίδιος, με εξαίρεση την περίπτωση της εισαγωγής/διαγραφής στην αρχή της λίστας. 85
Εισαγωγή/διαγραφή κόμβου στην αρχή της λίστας (3/9) Η εισαγωγή ενός κόμβου σε μια λίστα απαιτεί κάποιες ενέργειες (συνάρτηση bool SLList_Insert(TSLList **Head, TItem Item)): 1. Προσπαθούμε να δεσμεύσουμε χώρο για τον καινούργιο κόμβο. a. Αν δεν τα καταφέραμε, αναφέρουμε την αποτυχία και αποφεύγουμε περαιτέρω ενέργειες. b. Στην αντίθετη περίπτωση γεμίζουμε τον καινούργιο κόμβο με τα απαραίτητα δεδομένα. 86
Εισαγωγή/διαγραφή κόμβου στην αρχή της λίστας (4/9) 2. Ξεκινούμε τη διάσχιση της λίστας θέτοντας Curr = *Head και Prev = NULL, όπως έχουμε ήδη εξηγήσει. 3. Διασχίζουμε τη λίστα μέχρι να βρούμε το στοιχείο που ψάχνουμε, ώστε να εισάγουμε το καινούργιο κόμβο πριν από αυτό το στοιχείο, ή να φτάσουμε στο τέλος της λίστας. 87
Εισαγωγή/διαγραφή κόμβου στην αρχή της λίστας (5/9) 4. Ελέγχουμε αν μετά το βήμα 3 η εισαγωγή θα γίνει ακριβώς μετά την κεφαλή (Prev == NULL) ή πιο κάτω στη λίστα, και εκτελούμε τα κατάλληλα βήματα σύνδεσης του καινούργιου κόμβου. 5. Αναφέρουμε την επιτυχία εκτέλεσης της παρούσας συνάρτησης. 88
Εισαγωγή/διαγραφή κόμβου στην αρχή της λίστας (6/9) Για ακόμη μία φορά παρατηρούμε ότι η επιτυχία της συγκεκριμένης συνάρτησης εξαρτάται από την επιτυχία δέσμευσης μνήμης για τον καινούργιο κόμβο. Η συνάρτηση αυτή δυνητικά μεταβάλλει τη λίστα. Η διαγραφή ενός κόμβου από μια λίστα γίνεται με τη συνάρτηση bool SLList_RemoveFirst(TSLList **Head, TItem Item). 89
Εισαγωγή/διαγραφή κόμβου στην αρχή της λίστας (7/9) Σημειώνουμε ότι αυτή η συνάρτηση αποδεσμεύει τον πρώτο κόμβο που περιέχει το στοιχείο Item. Στον κώδικα υπάρχει συνάρτηση που αφαιρεί όλους τους κόμβους που περιέχουν το στοιχείο Item. 90
Εισαγωγή/διαγραφή κόμβου στην αρχή της λίστας (8/9) Οι ενέργειες της συγκεκριμένης συνάρτησης: 1. Ξεκινούμε τη διάσχιση της λίστας θέτοντας Curr = *Head και Prev = NULL, όπως έχουμε ήδη εξηγήσει. 2. Εφόσον δεν έχουμε φτάσει στο τέλος της λίστας και έχουμε βρει το προς διαγραφή στοιχείο: a. Ελέγχουμε αν ο προς αποδέσμευση κόμβος βρίσκεται ακριβώς μετά την κεφαλή της λίστας, παρακάτω. b. Εκτελούμε τα κατάλληλα βήματα παράκαμψης του προς αποδέσμευση κόμβου. c. Αναφέρουμε την επιτυχία εκτέλεσης της παρούσας συνάρτησης. 3. Σε περίπτωση αποτυχίας του βήματος 2, επιστρέφουμε false, δηλαδή δε βρέθηκε κόμβος που περιέχει το προς διαγραφή στοιχείο. 91
Εισαγωγή/διαγραφή κόμβου στην αρχή της λίστας (9/9) Η συνάρτηση αυτή δεν είναι κατάλληλη για την αφαίρεση όλων των κόμβων που περιέχουν το στοιχείο που θέλουμε να διαγράψουμε. Ο κώδικας που έχουμε παραθέσει περιέχει και μια συνάρτηση που, βασιζόμενοι στην υπόθεση ότι μας έχει επιτραπεί η εισαγωγή διπλοτύπων σε οποιοδήποτε σημείο της λίστας, αποδεσμεύει όλους τους κόμβους που περιέχουν το προς διαγραφή στοιχείο. Η συνάρτηση αυτή δυνητικά μεταβάλλει τη λίστα. 92
Αποδέσμευση όλων των κόμβων Η αποδέσμευση όλων των κόμβων μιας λίστας γίνεται με τη βοήθεια της συνάρτησης void SLList_Deallocate(TSLList **Head). Η τεχνική που χρησιμοποιούμε εδώ είναι απλή, και την έχουμε συναντήσει και στις περιπτώσεις των δυναμικών στοιβών και ουρών: Με τη βοήθεια ενός προσωρινού δείκτη, διασχίζουμε τη λίστα διαγράφοντας έναν-έναν τους κόμβους. Η συνάρτηση αυτή δυνητικά μεταβάλλει τη λίστα (εφόσον έχει στοιχεία). 93
Παράδειγμα (1/4) Εδώ θα παραθέσουμε ένα λεπτομερές παράδειγμα υλοποίησης της επιτόπου συγχώνευσης δύο συνδεδεμένων λιστών. Εκφώνηση: Καλείστε να συγχωνεύσετε μία μονή συνδεδεμένη λίστα με μία άλλη, χωρίς όμως τη δέσμευση πρόσθετου χώρου (δηλ. επιπλέον κόμβων). Αναγκαστικά η μία από τις δύο λίστες θα περιέχει όλα τα στοιχεία, και η άλλη θα αδειάσει πλήρως. 94
Παράδειγμα (2/4) Εκφώνηση (Συνέχεια): Συνεπώς, η συγχώνευση αυτή θα γίνει με την κατάλληλη εναλλαγή των δεικτών «Next» των κόμβων. Έτσι, δε δεσμεύουμε επιπλέον χώρο, αλλά χρησιμοποιούμε αυτόν που ήδη υπάρχει. Με αυτόν τον τρόπο δεν υπάρχει η πιθανότητα να αποτύχει η συγκεκριμένη λειτουργία, αφού όλος ο χώρος που χρειαζόμαστε έχει ήδη δεσμευτεί. 95
Παράδειγμα (3/4) Εκφώνηση (Συνέχεια): Αυτό αποτελεί ένα καλό παράδειγμα οικονομικής συγχώνευσης, αφού η «απλοϊκή» συγχώνευση δύο λιστών σε μία τρίτη απαιτεί χώρο ίσο με τον ήδη δεσμευμένο. Δηλαδή, αυτή η λειτουργία που καλείστε να υλοποιήσετε μπορεί να χρησιμοποιηθεί σε περίπτωση που το ζήτημα του χώρου είναι σημαντικό. Για λόγους απλότητας δεν απαιτείται η αφαίρεση των πιθανών διπλότυπων τιμών. 96
Παράδειγμα (4/4) Σε τεχνικό επίπεδο μπορούμε να εξετάζουμε αρκετά ζητήματα. Είναι συνετό να διασχίσουμε κάθε μία λίστα το πολύ μία φορά (σε λίγο θα γίνει πλήρως κατανοητό τι εννοούμε με το πολύ μία φορά). 97
Ζητήματα (1/12) Μια απλοϊκή αντιμετώπιση θα ήταν η διάσχιση της μίας λίστας κατά το πλήθος των στοιχείων της άλλης. Αυτό είναι σπάταλο, αφού στην ουσία περιέχει βρόχο μέσα σε βρόχο. Χρησιμοποιούμε ένα βρόχο ο οποίος επαναλαμβάνεται μόνο όσες φορές χρειάζεται. 98
Ζητήματα (2/12) Πρέπει να προβλεφθεί η περίπτωση μία από τις δύο (ή και οι δύο) λίστες να είναι κενές. Συνεπώς θα ισχύει μία από τις εξής τέσσερις περιπτώσεις: Η λίστα προορισμού να είναι κενή. Η λίστα προέλευσης να είναι κενή. Και οι δύο λίστες να είναι κενές. Καμία από τις δύο λίστες δεν είναι κενή. 99
Ζητήματα (3/12) Ο κώδικας πρέπει να είναι κομψός, αποφεύγοντας εντολές τύπου break και goto, που δημιουργούν δυσνόητο κώδικα τύπου «σπαγγέτι». Τερματίζουμε τη συνάρτηση, ή αλλιώς εκτελούμε μόνο την απολύτως ελάχιστη δουλειά που απαιτείται, όσο το δυνατόν πιο νωρίς, χωρίς να θυσιάζουμε την ορθότητα της λύσης. 100
Ζητήματα (4/12) Αν η μία λίστα είναι μικρότερη (σε πλήθος κόμβων) από την άλλη, όπως είναι απολύτως λογικό να συμβαίνει, δεν υπάρχει λόγος διάσχισης του υπόλοιπου της μεγαλύτερης, όταν συνδεθεί κατάλληλα και ο τελευταίος κόμβος της μικρότερης. 101
Ζητήματα (5/12) Μπορούμε να χειριστούμε τη λίστα προέλευσης (τη δεύτερη της οποίας τα στοιχεία θα συνδεθούν με τα στοιχεία της πρώτης) ως στοίβα. Έτσι, απλά θα αφαιρούμε πάντοτε το πρώτο στοιχείο μετά την κεφαλή της, και κατόπιν θα το συνδέουμε στην κατάλληλη θέση στην πρώτη λίστα. Αυτό συνδυάζεται κομψά με το επόμενο σημείο που αναφέρουμε. 102
Ζητήματα (6/12) Στο τέλος της συγχώνευσης πρέπει να βεβαιωθούμε ότι ο δείκτης της δεύτερης λίστας είναι NULL. Χρησιμοποιήστε τον κώδικα που σας έχει δοθεί, ώστε να επικεντρωθείτε στη μόνο στην καινούργια συνάρτηση και στον έλεγχό της στη main. Εδώ θα χρειαστείτε μόνο τις συναρτήσεις της αρχικοποίησης, της αποδέσμευσης, της εισαγωγής, και της εκτύπωσης των στοιχείων μιας λίστας. 103
Ζητήματα (7/12) Στο παρακάτω σχήμα φαίνονται τα βήματα μετακίνησης ενός κόμβου της 2ης λίστας στην πρώτη. Τα διακεκομμένα βέλη αντιστοιχούν στις εντολές (1) ως (5) εντός του εξωτερικού βρόχου του κώδικα της απάντησης. Στην περίπτωση που η λίστα προορισμού είναι μικρότερη από τη λίστα προέλευσης, συνδέουμε τον τελευταίο δείκτη Next της 1ης λίστας με το κατάλληλο στοιχείο της 2ης, και σταματούμε την περαιτέρω επεξεργασία. 104
Ζητήματα (8/12) Στην περίπτωση που η λίστα προορισμού (η 1η) είναι μεγαλύτερη της λίστας προέλευσης (η 2η), τότε κάνουμε την αντίστροφη διαδικασία, συνδέοντας τον τελευταίο δείκτη Next της 2ης με το κατάλληλο στοιχείο της 1ης, σταματώντας πάλι την περαιτέρω επεξεργασία. 105
Ζητήματα (9/12) Εικόνα 2. Βήματα μετακίνησης ενός κόμβου της 2ης λίστας στην πρώτη. Πηγή: Διδάσκων (2015). 106
Ζητήματα (10/12) Όπως έχουμε αναφέρει επανειλημμένα, οι δείκτες Prev και Curr πρέπει πάντοτε να απέχουν κατά ένα μόνο στοιχείο. Έτσι φροντίζουμε για τη μετακίνηση του δείκτη Prev κατά μία θέση (δηλαδή στο νεοεισαχθέν στοιχείο), ώστε να μπορέσουμε ξανά να παρεμβάλουμε άλλο στοιχείο, αν και όταν χρειαστεί. 107
Ζητήματα (11/12) Έτσι, όταν ο Curr γίνει NULL, τότε η αντίστοιχη λίστα έχει τελειώσει, αλλά συνεχίζουμε να έχουμε δείκτη (τον Prev) στο τελευταίο στοιχείο της, ώστε να μπορέσουμε να προσθέσουμε επιπλέον στοιχεία στο τέλος της. Ακόμη, όταν ο Prev είναι NULL, τότε ο Curr θα δείχνει στο πρώτο στοιχείο μιας λίστας, αν αυτό υπάρχει. Έτσι μπορούμε να χειριστούμε την ειδική περίπτωση της εισαγωγής ενός νέου στοιχείου ανάμεσα στην κεφαλή και στο προηγούμενο πρώτο στοιχείο μιας λίστας. 108
Ζητήματα (12/12) Μια ενδεικτική απάντηση του παραπάνω σεναρίου βρίσκεται στη συνάρτηση: void SLList_MergeInPlace(TSLList **L1, TSLList **L2) Παρατηρήστε ότι αυτή η συνάρτηση, βάσει των παραπάνω, δεν αποτυγχάνει, κι έτσι έχει δηλωθεί ότι επιστρέφει void. 109
Ασκήσεις (1/8) Άσκηση 1. Ελέγξτε αν η συνάρτηση SLList_InsertUnique λειτουργεί και εισάγει μόνο μία φορά το στοιχείο που της δίνετε. Άσκηση 2. Ελέγξτε αν η συνάρτηση SLList_RemoveAll λειτουργεί και διαγράφει όλες τις εμφανίσεις ενός συγκεκριμένου στοιχείου. Τι γίνεται αν προσπαθήσετε να διαγράψετε ένα στοιχείο που δεν υπάρχει; 110
Ασκήσεις (2/8) Άσκηση 3. Ελέγξτε αν η συνάρτηση SLList_RemoveDuplicates λειτουργεί και διαγράφει όλες τις εμφανίσεις όλων των στοιχείων που επαναλαμβάνονται. Τι γίνεται αν η λίστα δεν εμφανίζει διπλότυπα; Τι γίνεται αν όλα τα στοιχεία είναι ίδια; 111
Ασκήσεις (3/8) Άσκηση 4. Η συνάρτηση bool SLList_Merge(TSLList *List1, TSLList *List2, TSLList **NewList) είναι ημιτελής και έχει σκοπό τη συγχώνευση δύο λιστών σε μία τρίτη. Σας έχει δοθεί ένας σκελετός του κώδικά της, με τα επίμαχα σημεία να λείπουν. Συμπληρώστε τα. Άσκηση 5. Προσπαθήστε να υλοποιήσετε τη συνάρτηση SLList_MergeInPlace με πιο κομψό και ενδεχομένως καλύτερο τρόπο. Βοήθεια: Αφήστε το χειρισμό των ειδικών περιπτώσεων μετά το γενικό βρόχο. 112
Ασκήσεις (4/8) Άσκηση 6. Προσπαθήστε να υλοποιήσετε τη συνάρτηση SLList_Merge με πιο κομψό και ενδεχομένως καλύτερο τρόπο. Βοήθεια: Αφήστε το χειρισμό των ειδικών περιπτώσεων μετά το γενικό βρόχο. 113
Ασκήσεις (5/8) Άσκηση 7. Υλοποιήστε τη συνάρτηση SLList_MergeUnique, σύμφωνα με τη δήλωση που εμφανίζεται στον κώδικα. Η συνάρτηση αυτή συγχωνεύει δύο λίστες σε μία τρίτη, με την αφαίρεση διπλοτύπων. Επιστρέφει true αν τα κατάφερε (δηλαδή δεν αντιμετώπισε πρόβλημα δέσμευσης μνήμης), αλλιώς επιστρέφει false. 114
Ασκήσεις (6/8) Άσκηση 8. Δηλώστε και υλοποιήστε μια εκδοχή της SLList_MergeInPlace όπου παρακάμπτονται τα διπλότυπα. Άσκηση 9. Υλοποιήστε τη συνάρτηση SLList_Copy, σύμφωνα με τη δήλωση που εμφανίζεται στον κώδικα. Η συνάρτηση αυτή δημιουργεί αντίγραφο μιας λίστας. Αν η λίστα προορισμού περιέχει στοιχεία, τότε αυτά να αντικατασταθούν με το βέλτιστο δυνατό τρόπο με αυτά της λίστας προέλευσης. 115
Ασκήσεις (7/8) Άσκηση 10. Τροποποιήστε την υλοποίηση της μονής συνδεδεμένης λίστας με τρόπο που να διατηρεί ανά πάσα στιγμή ένα μετρητή των κόμβων που περιέχει. Χρησιμοποιήστε ιδέες από προηγούμενες ενότητες. 116
Ασκήσεις (8/8) Άσκηση 11. Δηλώστε και υλοποιήστε συνάρτηση που αντιστρέφει μία δεδομένη λίστα. Μπορείτε να υλοποιήσετε τη συνάρτηση αποφεύγοντας να δημιουργήσετε καινούργιους κόμβους, χρησιμοποιώντας μόνο αυτούς που έχετε (ώστε να μην υπάρχει περίπτωση αποτυχίας λόγω μη δέσμευσης επιπλέον μνήμης); 117
Βιβλιογραφία (1/8) Aho AV, Hopcroft JE, and Ullman JD. (1974) The design and analysis of computer algorithms. USA, Addison-Wesley Publishing Company. 470 pp. ISBN 0-201-00029-6. ( 38.33, 46.76). Aho AV, Hopcroft JE, and Ullman JD. (1983) Data structures and algorithms. USA, Addison-Wesley Publishing Company. 427 pp. ISBN 0-201-00023-7. ( 43.65, 53.25). Bik AJC. (2004) The software vectorization handbook: applying multimedia extensions for maximum performance. USA, Intel Press. 236 pp. ISBN 0-9743649-2-4. ( 32.31, 46.63). 118
Βιβλιογραφία (2/8) Carrano FM and Henry T. (2013) Data abstraction and problem solving with C++: walls and mirrors. 6th ed. UK, Pearson Education Limited. 833 pp. ISBN10 0-273-76841-7, ISBN13 978-0-76841-8. Cormen TH, Leiserson CE, Rivest RL, and Stein C. (2012) Εισαγωγή στους αλγορίθμους. USA/Ελλάδα, MIT Press/Ίδρυμα Τεχνολογίας & Έρευνας - Πανεπιστημιακές Εκδόσεις Κρήτης. 1260 pp. ISBN 978-960-524-224-4. Dasgupta S, Papadimitriou C and Vazirani U. (2009) Αλγόριθμοι. Ελλάδα, Εκδόσεις Κλειδάριθμος. 416 σελ. ISBN13 978-960-461-211-6. 119
Βιβλιογραφία (3/8) Gerber R, Bik AJC, Smith KB and Tian X. (2006) The software optimization cookbook: high-performance recipes for IA-32 platforms. 2nd ed. USA, Intel Press. 404 pp. ISBN 0-9764832-1-1. ( 35.07, 50.61). Helman P and Veroff R. (1988) Walls and mirrors: intermediate problem solving and data structures. Modula 2 ed. USA, The Benjamin/Cummings Publishing Company, Inc. 625 pp. ( 23.95). Kleinberg J and Tardos W. (2008) Σχεδιασμός αλγορίθμων. Ελλάδα, Εκδόσεις Κλειδάριθμος. 944 σελ. ISBN13 978-960- 461-207-9. 120
Βιβλιογραφία (4/8) Knuth DE. (2009) Η τέχνη του προγραμματισμού: θεμελιώδεις αλγόριθμοι, Τόμος Α. 3η Έκδοση. Ελλάδα, Εκδόσεις Τζιόλα. 759 σελ. ISBN13 978-960-418-185-8. Knuth DE. (2010) Η τέχνη του προγραμματισμού: ημιαριθμητικοί αλγόριθμοι, Τόμος Β. 3η Έκδοση. Ελλάδα, Εκδόσεις Τζιόλα. 912 σελ. ISBN13 978-960-418-224-4. Knuth DE. (2010) Η τέχνη του προγραμματισμού: ταξινόμηση και αναζήτηση, Τόμος Γ. 2η Έκδοση. Ελλάδα, Εκδόσεις Τζιόλα. 926 σελ. ISBN13 978-960-418-245-9. 121
Βιβλιογραφία (5/8) Kruse RL and Ryba AJ. (1999) Data structures and program design in C++. USA, Prentice Hall. 717 pp. ISBN 0-13- 082640-5. ( 40.74). Lafore R. (2005) Δομές δεδομένων και αλγόριθμοι στη Java. Ελλάδα, Εκδόσεις Μ. Γκιούρδα. 798 σελ. ISBN10 960-512- 452-1. Levitin A. (2008) Εισαγωγή στην ανάλυση και σχεδίαση αλγορίθμων. 2η Έκδοση. Ελλάδα, Εκδόσεις Τζιόλα. 700 σελ. ISBN13 978-960-418-143-8. 122
Βιβλιογραφία (6/8) McConnell S. (1993) Code complete: a practical handbook of software construction. USA, Microsoft Press. 857 pp. ( 25.99). Mehlhorn K and Sanders P. (2008) Algorithms and data structures: the basic toolbox. Germany, Springer-Verlag Berlin Heidelberg. 300 pp. ISBN13 978-3-540-77977-3. ( 28.11, 34.29). Prichard JJ and Carrano FM. (2011) Data abstraction and problem solving with Java: walls and mirrors. 3rd ed. UK, Pearson Education Limited. 959 pp. ISBN10 0-273-75120-4. ISBN13 978-0-273-75120-5. 123
Βιβλιογραφία (7/8) Sahni S. (2004) Δομές Δεδομένων, Αλγόριθμοι, και Εφαρμογές στη C++. Ελλάδα/ΗΠΑ, Εκδόσεις Τζιόλα/McGraw-Hill. 830 σελ. ISBN10 960-418-030-4. Sedgewick R and Flajolet P. (2013) An introduction to the analysis of algorithms. 2nd ed. USA, Pearson Education, Inc. 572 pp. ISBN13 978-0-321-90575-8. Sedgewick R and Wayne K. (2011) Algorithms. 4th ed. USA, Pearson Education, Inc. 952 pp. ISBN13 978-0-321-57351-3. Κοίλιας Χ. (2004) Δομές δεδομένων & οργανώσεις αρχείων. Ελλάδα, Εκδόσεις Νέων Τεχνολογιών. 447 σελ. ISBN10 960-8105-64-1. 124
Βιβλιογραφία (8/8) Μποζάνης ΠΔ. (2006) Δομές Δεδομένων. Ελλάδα, Εκδόσεις Τζιόλα. 552 σελ. ISBN10 960-418-084-3. Μποζάνης ΠΔ. (2009) Προβλήματα & ασκήσεις στους αλγορίθμους. Ελλάδα, Εκδόσεις Τζιόλα. 492 σελ. ISBN13 978-960-418-186-5. Μποζάνης ΠΔ. (2013) Αλγόριθμοι. Ελλάδα, Εκδόσεις Τζιόλα. 549 σελ. ISBN13 978-960-418-070-1. Μυσιρλής Ν. (2002) Δομές δεδομένων με C. Ελλάδα, αυτοέκδοση. 347 σελ. ISBN10 960-92031-1-6. Παπαρίζος Κ. (2010) Ανάλυση και σχεδίαση αλγορίθμων. Ελλάδα, Εκδόσεις Τζιόλα. 606 σελ. ISBN13 978-960-418-222-0. 125
Τέλος Ενότητας
Σημείωμα Αναφοράς Copyright ΤΕΙ Δυτικής Μακεδονίας, Δρ. Γεώργιος Σίσιας. «Δομές Δεδομένων». Έκδοση: 1.0. Κοζάνη 2015. Διαθέσιμο από τη δικτυακή διεύθυνση: URL. 127
Σημείωμα Αδειοδότησης Το παρόν υλικό διατίθεται με τους όρους της άδειας χρήσης Creative Commons Αναφορά, Μη Εμπορική Χρήση Παρόμοια Διανομή 4.0 [1] ή μεταγενέστερη, Διεθνής Έκδοση. Εξαιρούνται τα αυτοτελή έργα τρίτων π.χ. φωτογραφίες, διαγράμματα κ.λ.π., τα οποία εμπεριέχονται σε αυτό και τα οποία αναφέρονται μαζί με τους όρους χρήσης τους στο «Σημείωμα Χρήσης Έργων Τρίτων». [1] http://creativecommons.org/licenses/by-nc-sa/4.0/ Ως Μη Εμπορική ορίζεται η χρήση: που δεν περιλαμβάνει άμεσο ή έμμεσο οικονομικό όφελος από την χρήση του έργου, για το διανομέα του έργου και αδειοδόχο. που δεν περιλαμβάνει οικονομική συναλλαγή ως προϋπόθεση για τη χρήση ή πρόσβαση στο έργο. που δεν προσπορίζει στο διανομέα του έργου και αδειοδόχο έμμεσο οικονομικό όφελος (π.χ. διαφημίσεις) από την προβολή του έργου σε διαδικτυακό τόπο. Ο δικαιούχος μπορεί να παρέχει στον αδειοδόχο ξεχωριστή άδεια να χρησιμοποιεί το έργο για εμπορική χρήση, εφόσον αυτό του ζητηθεί. 128
Διατήρηση Σημειωμάτων Οποιαδήποτε αναπαραγωγή ή διασκευή του υλικού θα πρέπει να συμπεριλαμβάνει: το Σημείωμα Αναφοράς. το Σημείωμα Αδειοδότησης. τη δήλωση Διατήρησης Σημειωμάτων. το Σημείωμα Χρήσης Έργων Τρίτων (εφόσον υπάρχει). μαζί με τους συνοδευόμενους υπερσυνδέσμους. 129