מבני נתונים ויעילות אלגוריתמים

Slides:



Advertisements
Παρόμοιες παρουσιάσεις
Ε. ΠετράκηςΛίστες1  Λίστα: πεπερασμένη σειρά στοιχείων ίδιου τύπου  Οι πράξεις εξαρτώνται από τον τύπο της λίστας και όχι από τον τύπο δεδομένων  Λίστα:
Advertisements

Συλλογές, Στοίβες και Ουρές Σε πολλές εφαρμογές μας αρκεί η αναπαράσταση ενός δυναμικού συνόλου με μια δομή δεδομένων η οποία δεν υποστηρίζει την αναζήτηση.
POINTERS, AGGREGATION, COMPOSITION. POINTERS TO OBJECTS.
Ανασκόπηση σε Δείκτες, Ουρές, Στοίβες, Συνδεδεμένες Λίστες
ΜΑΘΗΜΑ 7ο Κυκλικές και Διπλά Συνδεδεμένες Λίστες,
ΕΠΛ 231 – Δομές Δεδομένων και Αλγόριθμοι
Λίστες παράλειψης (skip lists) TexPoint fonts used in EMF. Read the TexPoint manual before you delete this box.: AA A A A
Γλώσσα C & Unix Τμήμα Πληροφορικής, ΑΠΘ B’ εξάμηνο
Lab 3: Sorted List ΕΠΛ231-Δομές Δεδομένων και Αλγόριθμοι18/10/2010.
11-1 ΜΑΘΗΜΑ 12 ο Γράφοι, Διάσχιση Γράφων Υλικό από τις σημειώσεις Ν. Παπασπύρου, 2006.
ΗΥ150 – Προγραμματισμός Ξενοφών Ζαμπούλης ΗΥ-150 Προγραμματισμός Ταξινόμηση και Αναζήτηση.
Ε. ΠετράκηςΣτοίβες, Ουρές1 Στοίβες  Στοίβα: περιορισμένη ποικιλία λίστας  τα στοιχεία μπορούν να εισαχθούν ή να διαγραφούν μόνο από μια άκρη : λίστες.
Δυναμικη Δεσμευση Μνημης Συνδεδεμενες Λιστες (dynamic memory allocation, linked lists) Πως υλοποιουμαι προγραμματα που δεν γνωριζουμε πριν την εκτελεση.
Ταξινόμηση και Αναζήτηση
ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ Γενικευμένες κλάσεις Συλλογές.
6-1 ΜΑΘΗΜΑ 6 ο Ανασκόπηση σε Δείκτες, Συνδεδεμένες Λίστες, Ουρές, Στοίβες.
ΛΟΓ102: Τεχνολογία Λογισμικού Ι Διδάσκων: Νίκος Παπασπύρου 1Νίκος ΠαπασπύρουΛΟΓ102:
ΗΥ150 – Προγραμματισμός Ξ. Ζαμπούλης ΗΥ-150 Προγραμματισμός Δομές Δεδομένων.
ΛΟΓ102: Τεχνολογία Λογισμικού Ι Διδάσκων: Νίκος Παπασπύρου 1Νίκος ΠαπασπύρουΛΟΓ102:
Lab 3: Sorted List ΕΠΛ231-Δομές Δεδομένων και Αλγόριθμοι115/4/2015.
Λίστες παράλειψης (skip lists) TexPoint fonts used in EMF. Read the TexPoint manual before you delete this box.: AA A A A
ΗΥ 150 – Προγραμματισμός Ξενοφών Ζαμπούλης 1 Δείκτες σε συναρτήσεις Δείκτης σε συνάρτηση – Περιέχει τη διεύθυνση του κώδικα της συνάρτησης – Ό π ως ένας.
ΠΡΟΓΡΑΜΜΑΤΙΣΤΙΚΕΣ ΤΕΧΝΙΚΕΣ Διδάσκοντες:Γιάννης Μαΐστρος Στάθης Ζάχος Νίκος Παπασπύρου
9-1 ΜΑΘΗΜΑ 9 ο Δυαδικά Δένδρα, Διάσχιση Δυαδικών Δένδρων Υλικό από τις σημειώσεις Ν. Παπασπύρου, 2006.
ΠΡΟΓΡΑΜΜΑΤΙΣΤΙΚΕΣ ΤΕΧΝΙΚΕΣ Διδάσκοντες:Γιάννης Μαΐστρος Στάθης Ζάχος Νίκος Παπασπύρου
ΗΥ 150 – ΠρογραμματισμόςΞενοφών Ζαμ π ούλης ΗΥ -150 Προγραμματισμός Δομές Δεδομένων.
HY150Ξενοφών Ζαμπούλης HY150 Ε π ι π λέον στοιχεία της C.
Δομές Δεδομένων και Αρχεία Ενότητα 7: Η δομή Στοίβα Ηλίας Κ. Σάββας, Αναπληρωτής Καθηγητής, Τμήμα Μηχανικών Πληροφορικής Τ.Ε., T.E.I. Θεσσαλίας.
Δομές Δεδομένων και Αρχεία Ενότητα 10: Κυκλικά και Διπλά Συνδεδεμένη Λίστα Ηλίας Κ. Σάββας, Αναπληρωτής Καθηγητής, Τμήμα Μηχανικών Πληροφορικής Τ.Ε., T.E.I.
Δομές Δεδομένων και Αρχεία
ΗΥ150 – ΠρογραμματισμόςΚώστας Παναγιωτάκης ΗΥ-150 Προγραμματισμός Επιπλέον στοιχεία της C.
ΛΟΓ102: Τεχνολογία Λογισμικού Ι Διδάσκων: Νίκος Παπασπύρου 1Νίκος ΠαπασπύρουΛΟΓ102:
Τεχνολογία και Προγραμματισμός Υπολογιστών Ενότητα 9: Αρχεία - Λίστες Επίκουρος Καθηγητής Χρήστος Μακρής Τμήμα Μηχανικών Η/Υ & Πληροφορικής Πανεπιστήμιο.
Πρωί ανοιξιάτικης μέρας σε μια συνοικία της Αθήνας …
ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ Παράδειγμα Κληρονομικότητας Γενικευμένες κλάσεις.
Nacionalno računovodstvo
ΧΡΗΣΤΟΓΛΟΥ ΙΩΑΝΝΗΣ ΓΕΝ
ΑΡΙΘΜΟΔΕΙΚΤΕΣ ΔΡΑΣΤΗΡΙΟΤΗΤΑΣ
Διαχείριση Κινδύνου* *Η σειρά παρουσιάσεων για το μάθημα «Διαχείριση Κινδύνου» βασίζεται στο σύγγραμμα των Σχοινιωτάκη, Ν., και Συλλιγάρδου Γ., «Διαχείριση.
Κεφάλαιο 6 οι φίλοι μας, οι φίλες μας
ΜΑΘΗΜΑΤΙΚΑ ΓΙΑ ΟΙΚΟΝΟΜΟΛΟΓΟΥΣ (Κ105)
Εισαγωγή στη Ρομποτική
Η προβληματική των γενικών σκοπών και των ειδικών στόχων:
ΔΙΑΛΕΞΗ 9 Τμηματοποίηση Αγοράς.
10η Διάλεξη Ταξινόμηση E. Μαρκάκης
Καλαματα Η ιστορία της.
Ορισμοί Ιεραρχικός Μη γραμμικός τύπος δεδομένων Γονέας – Παιδιά
ΕΙΣΑΓΩΓΗ ΣΤΗΝ ΕΠΙΣΤΗΜΗ ΤΩΝ ΥΠΟΛΟΓΙΣΤΩΝ
Η ΤΕΧΝΗ ΣΤΗΝ ΑΡΧΑΪΚΗ ΕΠΟΧΗ
Διδάσκων: Δρ. Τσίντζα Παναγιώτα
Οργανική Χημεία Ενότητα 1: Χημεία του Άνθρακα Χριστίνα Φούντζουλα
Σύντομη Παρουσίαση Τόμος 2. Κεφάλαιο 2 «Στοιχεία Επικοινωνίας»
Τ.Ε.Ι. Κρήτης Σχολή Τεχνολογικών Εφαρμογών Τμ. Μηχανικών Πληροφορικής
Διερεύνηση γραφήματος
ΕΛΕΥΘΕΡΟΣ ΧΡΟΝΟΣ.
2η Συνεδρίαση Επιτροπής Παρακολούθησης
Τεχνολογία και Προγραμματισμός Υπολογιστών
Εισαγωγή στη Διοικητική Λογιστική
18η Διάλεξη Ισορροπημένα δέντρα Ε. Μαρκάκης
Εισαγωγή στον Προγ/μό Υπολογιστών
EPL231 – Data Structures and Algorithms
«Ανάπτυξη εφαρμογής για τη διαχείριση μεθόδων αναζήτησης σε οπτικοποιημένο περιβάλλον»  Μπλάγας Χρήστος.
11η Διάλεξη Ταξινόμηση Quicksort και Ιδιότητες Δέντρων Ε. Μαρκάκης
ΑΛΚΟΟΛ ΚΑΠΝΙΣΜΑ ΝΑΡΚΩΤΙΚΑ ΤΥΧΕΡΑ ΠΑΙΧΝΙΔΙΑ ΗΛΕΚΤΡΟΝΙΚΑ ΜΕΣΑ
‘’ΚΟΛΛΗΤΟΥΜΠΙΝΑΚΙΑ’’
Αναδρομικές Εξισώσεις και Αφηρημένοι Τύποι Δεδομένων
ΤΕΧΝΙΚΕΣ Αντικειμενοστραφουσ προγραμματισμου
Πως λειτουργεί ο αισθητήρας απόστασης;
Αναδρομή Στην ενότητα αυτή θα μελετηθούν τα εξής επιμέρους θέματα:
Δομές Δεδομένων (Data Structures)
Μεταγράφημα παρουσίασης:

מבני נתונים ויעילות אלגוריתמים מכללת אורט כפר-סבא מבני נתונים ויעילות אלגוריתמים רשימה מקושרת מימוש מחסנית באמצעות רשימה מקושרת 03.12.14 אורי וולטמן uri.weltmann@gmail.com

חידה לחימום במשחק השחמט, מלך, כידוע, יכול לזוז למרחק של משבצת אחת במאונך, במאוזן או באלכסון. סטודנט הצליח להעביר את המלך על כל משבצת של לוח שח רגיל (8×8), כך שהמלך ביקר בכל משבצת פעם אחת בדיוק, והמסלול שלו הסתיים במשבצת ממנה התחיל. האם יתכן שמספר המהלכים האלכסוניים שהוא ביצע שווה ל-17?

רשימה מקושרת מבנה הנתונים העיקרי בו השתמשנו עד כה על מנת לייצג אוסף נתונים בזיכרון המחשב, היה מערך (array). מערך מאוחסן בצורה רציפה בזיכרון, ויש לכך יתרונות וחסרונות. מהם? נניח ונרצה למצוא מבנה נתונים שיאפשר לנו לייצג אוסף נתונים בזיכרון, אבל שיתגבר על חלק מהחולשות של מערך. אין חובה שיאוחסן באופן רציף בזיכרון, וכך נוכל להתגבר על בעיות שנוצרות בעת שמנסים לאחסן אוסף נתונים גדול מאוד. אם ננסה להכניס נתון חדש באמצע האוסף, לא נאלץ לבזבז זמן רב בהזזת האיברים שאחריו. דוגמא למבנה נתונים כזה הוא מבנה נתונים הנקרא רשימה מקושרת (linked list).

רשימה מקושרת רשימה מקושרת היא מבנה נתונים המורכב מאוסף של חוליות (nodes) המשורשרות ביניהן באמצעות מצביעים. כל חוליה מכילה, בדרך כלל, שדה של מידע (ולפעמים, כמה שדות כאלה), ושדה נוסף המכיל מצביע לחוליה הבאה. שדה המצביע של החוליה האחרונה ברשימה המקושרת מכיל את הערך NULL, המציין את סוף הרשימה. נחזיק מצביע לראש הרשימה (לחוליה הראשונה בשרשרת). הבדל בולט ומיידי בין רשימה מקושרת לבין מערך, הוא שברשימה, כדי להגיע לאיבר מסוים, צריך לסרוק את הרשימה החל מתחילתה, בניגוד למערך, שם ניתן לגשת ישירות לאיבר לפי אינדקס. head 144 5- 201 12 NULL

רשימה מקושרת כדי להגדיר חוליה ברשימה מקושרת, נשתמש במבנה (struct) המכיל שדה אינפורמציה ושדה של מצביע לחוליה הבאה. typedef int list_info; struct node_type { list_info info; struct node_type *next; }; נשים לב שהגדרת struct node_type היא מעט מעגלית, שכן אחד השדות במבנה הוא מצביע למשתנה מסוג מבנה. נוכל לתת למבנה שם מקוצר: typedef struct node_type node; head 144 5- 201 12 NULL

רשימה מקושרת הנה דוגמא לקטע קוד, היוצר את המבנה המתואר מטה: pos pos node *head, *pos; head = malloc(sizeof(node)); head->info = 144; pos = malloc(sizeof(node)); head->next = pos; pos->info = -5; pos->next = malloc(sizeof(node)); pos->next->info = 201; pos = pos->next; pos->next->info = 12; pos->next->next = NULL; pos pos head 144 5- 201 12 NULL

חיפוש איבר ברשימה מקושרת מעוניינים לכתוב פונקציה המקבלת בתור פרמטרים את המצביע head לראש רשימה מקושרת, ואת האיבר x. על הפונקציה להחזיר מצביע למופע הראשון של x הנמצא בה, או להחזיר NULL במידה ו-x לא מופיע ברשימה אפילו פעם אחת. node *list_search (node *head, list_info x) { node *pos = head; while ((pos != NULL) && (pos->info != x)) pos = pos->next; return pos; } pos pos pos pos pos head NULL

חיפוש איבר ברשימה מקושרת סוג החיפוש שעשינו הוא חיפוש לינארי (linear search). כזכור, סיבוכיות זמן הריצה של חיפוש כזה היא Θ(n), כאשר n הוא מספר האיברים ברשימה המקושרת. במידה ואיברי הרשימה המקושרת היו ממוינים, האם היינו יכולים להשתמש בחיפוש בינארי (binary search), ובכך להשיג זמן ריצה טוב יותר, של Θ(logn)? לא, משום שבכדי להשתמש בחיפוש בינארי, עלינו להיות מסוגלים לגשת ישירות לכל איבר באוסף הנתונים, באמצעות אינדקס. זה אפשרי כאשר מדובר במערך, אך לא כאשר מדובר ברשימה מקושרת, שבה בכדי להגיע לאיבר מסוים, צריך להתחיל בראש הרשימה, ולסרוק עד שפוגשים אותו.

הכנסת איבר לרשימה מקושרת כאשר רצינו להכניס איבר למקום באמצע מערך, היינו צריכים להזיז את כל האיברים שאחריו צעד אחד ימינה, כדי לפנות עבורו מקום. האם כך ננהג גם ברשימה? נניח שיש לנו את הערך x, ומצביע pos המצביע אל אחת החוליות ברשימה המקושרת. מעוניינים להוסיף חוליה חדשה לרשימה מקושרת, שיכיל את הערך x, ושיימצא אחרי המקום ש-pos מצביע עליו. נעשה זאת כך: node *pnew = malloc(sizeof(node)); pnew->info = x; pnew->next = pos->next; pos->next = pnew; pnew x pos head NULL

הכנסת איבר לראש רשימה מקושרת נניח, כמו קודם, כי נתון לנו הערך x, וכי מעוניינים להוסיף חוליה חדשה לרשימה המקושרת, המכילה את הערך x. אולם הפעם, נניח כי מעוניינים שהחוליה החדשה תתווסף בראש הרשימה (כלומר, שהמצביע head יצביע עליה). אילו שינויים צריך לעשות בקטע הקוד הקודם? node *pnew = malloc(sizeof(node)); pnew->info = x; pnew->next = pos->next; pos->next = pnew; head; head pnew x head NULL

מחיקת האיבר שבראש רשימה מקושרת נניח כי מעוניינים למחוק את החוליה הנמצאת בראש הרשימה המקושרת (בהנחה שהרשימה איננה ריקה, כלומר – יש בה לפחות חוליה אחת). כיצד נעשה זאת? pos = head; head = head->next; free(pos); נשים לב כי המצביע pos לא מצביע כעת אל אזור בזכרון אליו אמורה להיות לנו גישה, ולכן יש להקפיד לא להשתמש בו לאחר ביצוע הוראת ה-free. במקרים כאלו, לפעמים, נציב בו את הערך NULL, כדי למנוע מצב בו ניגש בטעות לכתובת שאליה הוא מצביע. /* head = pos->next; */ pos head head NULL

מחיקת איבר ברשימה מקושרת כאשר רצינו למחוק איבר באמצע מערך, היינו צריכים להזיז את כל האיברים שאחריו צעד אחד שמאלה. האם כך ננהג גם כשנרצה למחוק איבר ברשימה מקושרת? נניח כי המצביע pos מצביע אל אחת החוליות ברשימה המקושרת, שאיננה החוליה הראשונה, ואנו מעוניינים למחוק אותה. כיצד נעשה זאת? temp = head; while (temp->next != pos) temp = temp->next; temp->next = pos->next; free(pos); pos = temp->next; /* temp->next = temp->next->next */ temp temp pos pos head NULL

הכנסת איבר לרשימה מקושרת ממוינת אנחנו כבר יודעים איך מכניסים חוליה במקום כלשהו לרשימה מקושרת. כעת נכתוב קטע קוד המקבל ערך x ומצביע head לראש רשימה מקושרת לא ריקה הממוינת בסדר עולה. צריך להכניס חוליה חדשה עם הערך x במקום המתאים ברשימה, כדי לא "לקלקל" את היותה ממוינת. נדגים כיצד נכניס ערך (למשל, x = 8) לרשימה ממוינת: node *pos = head, *pnew; while ((pos->next != NULL) && (pos->next->info < x)) pos = pos->next; pnew = malloc(sizeof(node)); pnew->info = x; pnew->next = pos->next; pos->next = pnew; האם קטע קוד זה יעבוד כשורה אם הרשימה הממוינת ריקה? ואם היא מכילה חוליה אחד בלבד? ואם האיבר החדש קטן מכל האיברים שכעת ברשימה? או גדול מכל האיברים שכעת ברשימה? pnew 8 pos pos pos head 3 6 7 9 NULL

היפוך רשימה מקושרת האם קטע קוד זה יעבוד כשורה נניח שמקבלים מצביע head לראש רשימה מקושרת, ומעוניינים להפוך את סדר האיברים ברשימה, כך שהאיבר שקודם היה בראש הרשימה יהיה כעת בזנב הרשימה, והאיבר שקודם היה בזנב הרשימה יהיה כעת בראש הרשימה: node *prev = head, *curr = prev->next, *nxt = curr->next; prev->next = NULL; while (nxt != NULL) { curr->next = prev; prev = curr; curr = nxt; nxt = nxt->next; } head = curr; האם קטע קוד זה יעבוד כשורה אם הרשימה ריקה? ואם הרשימה מכילה חוליה אחת או שתיים? head prev prev curr nxt nxt curr prev curr nxt NULL head NULL

מחסנית בעבר ראינו כיצד אפשר לייצג את טיפוס הנתונים המופשט (טנ"מ) 'מחסנית', הן על-ידי מערך סטטי והן על-ידי מערך דינאמי. כשיישמנו את הטנ"מ בסביבת העבודה, הפרדנו בין הכותרות של הפונקציות (שנשמרו בקובץ stack.h) לבין גוף הפונקציות (שנשמרו בקובץ stack.c). כך השגנו הפרדה בין ממשק (interface) למימוש (implementation), שאיפשרה לנו לשנות את הייצוג של הטנ"מ בזיכרון, מבלי שנצטרך לשנות תוכניות שכבר נכתבו, שעושות שימוש במחסנית. מה הייתה הסיבה בגללה העדפנו לייצג מחסנית באמצעות מערך דינאמי ולא באמצעות מערך סטטי? גודלו של מערך סטטי נקבע מראש, ואי אפשר לאחסן בו יותר איברים, לעומת מערך דינאמי, שגודלו משתנה לפי הצורך. במערך סטטי עלול להיות בזבוז זכרון (אם מגדירים מערך גדול, ומשתמשים רק בחלק קטן ממנו), לעומת מערך דינאמי, שגודלו תמיד כפי הדרוש. `

מחסנית האם הייצוג באמצעות מערך דינאמי הוא נטול חולשות? אילו קשיים עשויים להתעורר במידה ונרצה לאחסן מספר גדול מאוד של איברים במחסנית? העובדה שמערך מאוחסן באופן רציף בזיכרון, פירושה שאם נרצה לאחסן מספר רב של איברים במחסנית, אז ייתכן ונתקשה למצוא בלוק זיכרון פנוי שהוא ארוך מספיק. האם הכרנו מבנה נתונים שיכול לעזור לנו להתמודד עם בעיה זו? אם נייצג את טיפוס הנתונים המופשט 'מחסנית' באמצעות רשימה מקושרת (linked list), אז גם אם המחסנית תכיל מספר רב של איברים, קרוב לוודאי שנוכל לאחסן אותה בזיכרון, שכן ברשימה מקושרת, החוליות לא חייבות להיות מאוחסנות אחת אחרי השנייה בזיכרון.

מחסנית נכלול בקובץ stack.h הגדרה של חוליה (node) ברשימה מקושרת, ונגדיר מחסנית בתור מבנה, המכיל בתוכו מצביע לחוליה הראשונה (ראש המחסנית): typedef int stack_item; struct node_type { stack_item info; struct node_type *next; }; typedef struct { struct node_type *top; } stack;

מחסנית הכותרות של הפונקציות ב-stack.h נותרות ללא שינוי: void stack_init(stack *s); int stack_empty (stack s); int stack_full (stack s); void stack_push (stack *s, stack_item x); stack_item stack_pop (stack *s); stack_item stack_top (stack s); השינויים שערכנו במימוש של המחסנית לא מחייבים לערוך שינויים בחתימות של הפונקציות שבממשק. מדוע? הפונקציות עדיין מקבלות את אותם הפרמטרים, מחזירות את אותם הערכים, ומקיימות את אותן טענות כניסה ויציאה. מבחינת מתכנת שמשתמש ביחידת הספרייה stack.h שאנחנו מספקים, השינוי שעשינו במימוש, לא משפיע בכלל על הממשק. מבחינת אותו מתכנת, העובדה ששינינו את הייצוג של טנ"מ מחסנית ממערך דינאמי לרשימה מקושרת, זה משהו שקרה "מאחורי הקלעים", ולא צריך להשפיע בכלל על האופן שבו הוא עובד עם יחידת הספרייה. כמו כן, הוא לא צריך לשכתב את התכניות שעושות שימוש ביחידה.

מחסנית בקובץ המימוש stack.c, נצטרך לערוך שינויים: void stack_init (stack *s) { s->top = NULL; } int stack_empty (stack s) return (s.top == NULL); int stack_full (stack s) return 0; למה במימוש באמצעות רשימה מקושרת, הפונקציה stack_full תחזיר תמיד 'שקר'? מתכנת מציע למחוק את הפונקציה stack_full מיחידת הספרייה (גם מהממשק וגם מהמימוש), שכן אין בה יותר צורך. מה אתם חושבים על הצעתו?

מחסנית כך נממש דחיפה למחסנית: כך נממש שליפה ממחסנית: void stack_push (stack *s, stack_item x) { struct node_type *temp = malloc(sizeof(struct node_type)); temp->info = x; temp->next = s->top; s->top = temp; } כך נממש שליפה ממחסנית: stack_item stack_pop (stack *s) if (!stack_empty(*s)) { struct node_type *temp = s->top; stack_item data = s->top->info; s->top = temp->next; free(temp); return data; איך ניתן היה להיעזר במצביע temp כדי לכתוב בצורה אחרת את השורה השנייה בבלוק שאחרי ה-if? האם הפונקציה stack_push כשורה גם אם המחסנית ריקה? האם שתי הפונקציות תפעלנה כשורה גם אם המחסנית מכילה איבר יחיד?

מחסנית הצצה למחסנית נכתבה כך, גם במימוש באמצעות מערך סטטי וגם במימוש באמצעות מערך דינאמי: stack_item stack_top (stack s) { if (!stack_empty(s)) return s.data[s.top]; } כיצד תראה אותה הפונקציה במימוש באמצעות רשימה מקושרת? return (s.top)->info;

מחסנית מהי סיבוכיות זמן הריצה של כל אחת מפעולות הממשק של טנ"מ מחסנית, אם הוא ממומש באמצעות רשימה מקושרת? במימוש מחסנית באמצעות רשימה מקושרת, בדומה למימוש באמצעות מערך סטטי או למימוש באמצעות מערך דינאמי, כל אחת מפעולות הממשק (איתחול, שליפה, דחיפה, בדיקה האם ריק, בדיקה האם מלא, הצצה) מבצעת כמות קבועה של עבודה, ללא קשר לשאלה כמה איברים נמצאים כרגע במחסנית. מכיוון שהפעולות הללו אינן תלויות בגודל n, הן מתבצעות בזמן קבוע Θ(1).

תרגיל סטודנט, הכותב תכנית המשתמשת ביחידת הספריה stack.h, מעוניין לכתוב פונקציה המקבלת כפרמטר מחסנית לא ריקה, ומחזירה 1 אם יש במחסנית יותר מאיבר יחיד, ו-0 אם יש במחסנית רק איבר אחד. הוא כתב את הפונקציה הבאה: int check_size (stack s) { return ((s.top)->next != NULL); } חברו, הפותר בעיה דומה, כתב את הפונקציה הזו: return (s.top > 0); מי מבין שתי הפונקציות נכונה יותר?

תרגיל שתי הפונקציות שגויות, משום ששתיהן ניגשות ישירות למימוש של טנ"מ מחסנית, במקום להשתמש בפעולות הממשק! כאשר משתמשים ביחידת הספרייה stack.h, אנחנו לא יודעים באיזה צורה בחרו לממש את המחסנית (במערך סטטי? במערך דינאמי? ברשימה מקושרת? בדרך אחרת?), ולכן, אין אנו יכולים להניח הנחות לגבי האופן שבו המחסנית מיוצגת בזיכרון. מבחינת נכונות, ומתוך שיקולים של הנדסת תוכנה, נסכים שכדי לגשת למחסנית, מחוץ לקובץ המימוש stack.c, מותר לנו להשתמש רק בפעולות הממשק: int check_size (stack s) { stack_pop(&s); return (!stack_empty(s)); } האם הפונקציה הזו תלויה במימוש שבו המתכנת שבנה את יחידת הספרייה stack.h בחר להשתמש? האם המתכנת שכתב את הפונקציה check_size הזו יודע כיצד מומשה יחידת הספרייה? האם זה משנה מבחינתו? אם נחליט לשנות את האופן שבו ממומשת המחסנית, האם נצטרך לשכתב פונקציה זו?

עוד תרגיל כזכור, בייצוג מחסנית באמצעות רשימה מקושרת, הגדרנו מחסנית באופן הבא: typedef struct { struct node_type *top; } stack; איזה יתרונות יש להגדרת מחסנית בתור מבנה המכיל מצביע לחוליה, במקום, למשל, להגדיר פשוט מחסנית בתור מצביע לחוליה הראשונה? typedef struct node_type *stack; כאשר מגדירים מחסנית בתור רשומה, אפשר לאחסן ברשומה שדות נוספים, מלבד שדה מצביע לחוליה הראשונה. לדוגמא, אפשר לאחסן שדה בשם num_items המונה את מספר איברי המחסנית. כשמאתחלים מחסנית חדשה באמצעות stack_init, ערכו של השדה num_items ייקבע לאפס. כשדוחפים או שולפים איבר מראש המחסנית, השדה num_items יגדל או ייקטן ב-1. כאשר רוצים לבדוק האם המחסנית ריקה, ניתן לבדוק האם ערכו של top הוא NULL (כפי שכתבנו), או לבדוק האם ערכו של השדה num_items הוא אפס. כעת אם נרצה, נוכל להוסיף לממשק של טנ"מ מחסנית, את פעולת הממשק stack_size המחזירה את מספר האיברים המאוחסן במחסנית. מה סיבוכיותה? ומה הייתה סיבוכיותה אלמלא היינו שומרים את השדה num_items? סיבוכיות הפעולה היא קבועה Θ(1), לעומת סיבוכיות לינארית Θ(n) ללא שדה זה.