Download presentation
Presentation is loading. Please wait.
1
מבני נתונים רשימה מקושרת, מחסנית ותור
קרן כליף
2
ביחידה זו נלמד: מהי רשימה מקושרת פונקציות של רשימות מקושרות
מימושים רקורסיביים לפונקציות של רשימות מקושרות ההבדל בין מערך לרשימה וריאציות של רשימות מקושרות תור ומחסנית
3
מבנה נתונים מבנה נתונים הוא אוסף המאפשר לנו להחזיק יותר מאיבר אחד
למשל: מערך קיימים מבני נתונים נוספים שלכל אחד יש את היתרונות והחסרונות שלו, ואלו ישפיעו על בחירת שימוש המבנה נתונים אחד או אחר היתרונות של מערך: גישה ישירה לאיבר הוספה בקלות לסוף המערך החסרונות של מערך: הסרה/הוספה לאמצע מורכבת גודל מוגבל
4
רשימה מקושרת זהו מבנה נתונים המאפשר הוספה דינאמית של איברים לכל מקום באוסף בקלות אין צורך להחליט מראש כמה איברים יהיו ברשימה הרעיון הוא יצירת איבר וקישורו למקום המתאים ברשימה בכל רגע נתון כל איבר ברשימה מקושרת הוא מבנה המכיל נתון והצבעה לאיבר הבא האיבר האחרון ברשימה יצביע ל- NULL בניגוד למערך, האיברים אינם ברצף בזיכרון typedef int type; typedef struct LNode } type data; struct LNode* next; } LNode; 4 9 6 typedef struct List } struct LNode* head; } List;
5
דוגמת הוספת ערכים לרשימה
lst head 1 ??? 2 2 void main() { List lst; lst.head = (LNode*)calloc(1, sizeof(LNode)); lst.head->data = 1; lst.head->next = (LNode*)calloc(1, sizeof(LNode)); lst.head->next->data = 2; }
6
פונקציה המחשבת את מספר הצמתים ברשימה
int getListLength(const List* lst) { int count = 0; LNode* temp = lst->head; while (temp != NULL) count++; temp = temp->next; } return count; count = 0 count = 1 count = 2 temp lst head 1 ??? 2 2
7
פונקציה המדפיסה את איברי הרשימה
void printList(const List* lst) { LNode* temp = lst->head; while (temp != NULL) printf("%d ", temp->data); temp = temp->next; } 1 2 1 temp lst head 1 ??? 2 2
8
פונקציה המשחררת את איברי הרשימה
void freeList(List* theList) { LNode* current = theList->head; LNode* next; while (current) next = current->next; free(current); current = next; } theList->head = NULL; current next lst 1 head ??? 2 2
9
שדרוג הרשימה עד כה החזקנו בתוך המבנה List מצביע לראש הרשימה, והוספת איבריו הייתה באמצעותו: void main() { List lst; lst.head = (LNode*)calloc(1, sizeof(LNode)); lst.head->data = 1; lst.head->next = (LNode*)calloc(1, sizeof(LNode)); lst.head->next->data = 2; lst.head->next->next = (LNode*)calloc(1, sizeof(LNode)); lst.head->next->next->data = 3; freeList(&lst); }
10
שדרוג הרשימה כדי להקל על הוספת איברים לסוף הרשימה נשדרג את המבנה List כך שיכיל גם מצביע לאיבר האחרון: void main() { List lst; lst.head = (LNode*)calloc(1, sizeof(LNode)); lst.head->data = 1; lst.tail = lst.head; lst.tail->next = (LNode*)calloc(1, sizeof(LNode)); lst.tail->next->data = 2; lst.tail = lst.tail->next; lst.tail->next->data = 3; freeList(&lst); } typedef struct List } struct LNode *head, *tail; } List; ניתן לראות שכעת הקוד של הוספת איבר לסוף הרשימה זהה, ובפרט חוסך לולאה המטיילת לאיבר האחרון, ולכן יעילות הפעולה היא (1)O lst 3 1 head 2 tail
11
פונקציה המאתחלת רשימה ריקה
פונקציה זו מאתחלת בשדות ה- head וה- tail את הערך NULL List makeEmptyList() { List lst; lst.head = lst.tail = NULL; return lst; }
12
פונקציה הבודקת האם רשימה ריקה
int isEmpty(const List* lst) { return lst->head == NULL; }
13
פונקציה שיוצרת איבר להכנסה לרשימה
הפונקציה תקבל את הערך שיהיה באיבר, ומצביע לאיבר הבא: פונקציה זו תשמש אותנו בפונקציות הבאות LNode* createNewNode(type newData, LNode* next) { LNode* newNode = (LNode*)calloc(1, sizeof(LNode)); newNode->data = newData; newNode->next = next; return newNode; }
14
הוספת ערך לראש הרשימה void insertValueToHead(List* lst, type newData) { LNode* newNode = createNewNode(newData, lst->head); if (isEmpty(lst)) lst->head = lst->tail = newNode; else lst->head = newNode; } 3 newData = 4 2 lst head tail 4 newNode
15
הוספת ערך לראש הרשימה (שהפעם ריקה)
void insertValueToHead(List* lst, type newData) { LNode* newNode = createNewNode(newData, lst->head); if (isEmpty(lst)) lst->head = lst->tail = newNode; else lst->head = newNode; } newData = 4 lst head tail 4 newNode
16
הוספת ערך לסוף הרשימה void insertValueToTail(List* lst, type newData) { LNode* newNode = createNewNode(newData, NULL); if (isEmpty(lst)) lst->head = lst->tail = newNode; else lst->tail->next = newNode; lst->tail = newNode; } 3 2 lst newData = 4 head tail 4 newNode
17
הוספת ערך לסוף הרשימה (שהפעם ריקה)
void insertValueToTail(List* lst, type newData) { LNode* newNode = createNewNode(newData, NULL); if (isEmpty(lst)) lst->head = lst->tail = newNode; else lst->tail->next = newNode; lst->tail = newNode; } lst newData = 4 head tail 4 newNode
18
הוספת איבר לסוף הרשימה void insertNodeToTail(List* lst, LNode* newNode) { if (isEmpty(lst)) lst->head = lst->tail = newNode; else lst->tail->next = newNode; lst->tail = newNode; }
19
דוגמאת החברים
20
דוגמא: פיצול רשימה לזוגיים ואי-זוגיים
void main() { List lst = makeEmptyList(); List lstEven, lstOdd; int i; for (i=1 ; i <= 10 ; i++) insertValueToTail(&lst, i); splitListToEvenAndOdd(&lst, &lstEven, &lstOdd); printf("There are %d even nodes: ", getListLength(&lstEven)); printList(&lstEven); printf("\n"); printf("There are %d odd nodes: ", getListLength(&lstOdd)); printList(&lstOdd); freeList(&lstEven); freeList(&lstOdd); }
21
דוגמא: פיצול רשימה לזוגיים ואי-זוגיים (2)
דוגמא: פיצול רשימה לזוגיים ואי-זוגיים (2) void splitListToEvenAndOdd(List* src, List* even, List* odd) { LNode* current = src->head; LNode* next; *even = makeEmptyList(); *odd = makeEmptyList(); while (current) next = current->next; if (current->data%2 == 0) insertNodeToTail(even, current); else insertNodeToTail(odd, current); current->next = NULL; current = next; } 3 1 2 src head tail even head tail current next odd head tail פונקציה זו הורסת את הרשימה המקורית. כדי לשמור עליה היה צריך להעתיק קודם את רשימת המקור.
22
העתקת רשימה List copyList(const List* lst) {
List res = makeEmptyList(); LNode* temp = lst->head; while (temp) insertValueToTail(&res, temp->data); temp = temp->next; } return res;
23
הרעיון מאחורי הוספת ערך לאמצע רשימה
דוגמה: הוספת הערך 8 אחרי הערך 9 ראשית נייצר איבר חדש נחבר את האיבר החדש לאיבר שאמור להיות אחריו נחבר לאיבר החדש את האיבר שלפניו 4 9 6 8
24
הוספת איבר לאמצע הרשימה (אחרי ערך מסוים)
void insertAfterValue(List* lst, type insertAfter, type newValue) { LNode* temp = lst->head; while (temp && temp->data != insertAfter) temp = temp->next; if (temp) LNode* newNode = createNewNode(newValue, temp->next); temp->next = newNode; הוספה לאחר איבר שלא קיים - לא תבוצע 2 9 6 insertAfter = 9 newValue = 8 head temp temp 8 newNode
25
5 2 9 מחיקת איבר void removeFirstValueOf(List* lst, type toRemove) {
LNode* prev = lst->head; LNode* temp = lst->head; while (temp && temp->data != toRemove) prev = temp; temp = temp->next; prev->next = temp->next; free(temp); 5 toRemove = 9 prev 2 9 head temp temp
26
2 9 6 חיפוש ערך LNode* findValue(List* lst, type lookFor) {
LNode* temp = lst->head; while (temp) if (temp->data == lookFor) return temp; temp = temp->next; return NULL; } lookFor = 9 2 9 6 head temp temp
27
2 4 1 7 2 דוגמא true false true void main() {
List list = makeEmptyList(); LNode* found; printf("Is Empty? %s\n", isEmpty(&list) ? "true" : "false"); insertValueToHead(&list, 4); insertValueToHead(&list, 7); insertValueToTail(&list, 1); insertAfterValue(&list, 4, 2); insertAfterValue(&list, 8, 9); // doesn't add.. insertAfterValue(&list, 1, 2); removeFirstValueOf(&list, 2); found = findValue(&list, 4); found = findValue(&list, 10); printList(&list); printf(“\nIs Empty? %s\n", isEmpty(&list) ? "true" : "false"); freeList(&list); 2 true 4 head 1 7 2 false true
28
מימושים רקורסיביים – שחרור רשימה
void freeListRec(List* lst) { freeListHelper(lst->head); lst->head = NULL; lst->tail = NULL; } void freeListHelper(LNode* head) { if (head == NULL) return; freeListHelper(head->next); free(head); }
29
מימושים רקורסיביים – הדפסת רשימה
void printListRec(List* lst) { printListHelper(lst->head); } void printListHelper(LNode* head) { if (head == NULL) printf("\n"); return; } printf("%d ", head->data); printListHelper(head->next);
30
מימושים רקורסיביים – חיפוש איבר
LNode* findValueRec(List* lst, type lookFor) { return findValueRec(lst->head, lookFor); } LNode* findValueHelper(LNode* head, type lookFor) { if (head == NULL) return NULL; if (head->data == lookFor) return head; return findValueRec(head->next, lookFor); }
31
מימושים רקורסיביים – אורך רשימה
int getListLengthRec(List* lst) { return getListLengthHelper(lst->head); } int getListLengthHelper(LNode* head) { if (head == NULL) return 0; return 1 + getListLengthHelper(head->next); }
32
דוגמאת ניהול הזכרון
33
וריאציות של רשימה מקושרת
עבודה עם dummy head: בעת הוספה/הסרה וכד' חוסך בדיקה האם האיבר הוא הראשון רשימה דו-כיוונית: יעיל עבור טיול דו כיווני, מאפשר לא לשמור את המשתנה prev בכל מקום בו צריך לשמור הפניה לאיבר הקודם (למשל במחיקה) מאפשר גישה מיידית לאיבר קודם מקל על פעולת החלפה של 2 איברים רשימה מעגלית האיבר האחרון מצביע לראשון
34
יתרונות/חסרונות לשימוש ברשימה מקושרת
יתרונות: ניתן להוסיף בקלות איברים לכל מקום באוסף ניתן להסיר איברים בקלות חסרונות: אין גישה ישירה לאיבר שימוש בהקצאה דינאמית עבור כל איבר חדש נשתמש כאשר הפעולות העיקריות על האוסף יהיו הוספה להתחלה, לאמצע, הסרה
35
רשימה מקושרת כללית פונקציות ההוספה / הסרה / הדפסה וכו' יכולות להיות כלליות ואין צורך שיהיו מוגבלות עבור טיפוס מסויים בלבד נבצע את השינוי הבא בהגדרת המבנה Node: נעדכן את המבנה List: typedef struct Node { void* data; struct Node* next; } LNode; typedef struct { struct LNode *head, *tail; void (*printNode)(void*); int (*compare)(void*, void*); } List;
36
רשימה מקושרת כללית – הקוד (1)
List makeEmptyList(void (*printNode)(void*), int (*compare)(void*, void*)) } List theList = { {NULL, NULL}, printNode, compare}; return theList ; { void insertValueToHead(List* theList , void* newValue) } LNode* newNode = (LNode*)calloc(1, sizeof(LNode)); newNode->data = newValue; if (isEmpty(theList )) theList ->head = theList ->tail = newNode; else { newNode->next = theList->head; theList->head = newNode;
37
רשימה מקושרת כללית – הקוד (2)
void insertValueToTail(List* theList , void* newValue) } LNode* newNode = (LNode*)calloc(1, sizeof(LNode)); newNode->data = newValue; if (isEmpty(theList )) theList>head = theList->tail = newNode; else { theList->theList->next = newNode; theList->tail = newNode; void printList(List* theList) { LNode* temp = theList->head; while (temp) theList->printNode(temp->data); temp = temp->next; printf("\n");
38
רשימה מקושרת כללית – הקוד (3)
LNode* findValue(List* theList, void* lookFor) { LNode* temp = theList->head; while (temp) if (theList->compare(temp->data, lookFor) == 0) return temp; temp = temp->next; return NULL;
39
רשימה מקושרת כללית – דוגמאת שימוש
void printStr(void* str) { printf("%s ", (char*)str); int compareStrings(void* str1, void* str2) } return strcmp((char*)str1, (char*)str2); { void main() { List list = makeEmptyList(printStr, compareStrings); char *str1="hi", *str2="bye", *str3="happy", *str4="birthday"; printf("Is Empty? %s\n", isEmpty(&list) ? "true" : "false"); insertValueToHead(&list, str1); insertValueToTail(&list, str2); insertValueToHead (&list, str3); insertValueToHead (&list, str4); printList(&list); freeList(&list);
40
תור תור הוא מבנה נתונים הפועל עפ"י העקרון של First In First Out
הפעולות שניתן לבצע על תור: בדיקה האם התור ריק: isEmpty הוספה לסוף התור: enqueue הסרה מראש התור: dequeue המימוש יהיה באמצעות רשימה מקושרת: הוספה תמיד תתבצע לסוף הסרה תמיד תבוצע מהראש
41
דוגמא לשימוש בתור void enqueue(List* l, void* newValue) }
insertToEnd(l, newValue); { void* dequeue(List* l) { return removeHead(l); void main() { List list = makeEmpty(printStr, compareStrings); char *str1="hi", *str2="bye", *str3="happy", *str4="birthday"; char* res; printf("Is Empty? %s\n", isEmpty(&list) ? "true" : "false"); enqueue(&list, str1); enqueue(&list, str2); enqueue(&list, str3); printList(&list); res = dequeue(&list); freeList(&list);
42
מחסנית מחסנית היא מבנה נתונים הפועל עפ"י העקרון של Lifo In First Out
הפעולות שניתן לבצע על מחסנית: בדיקה האם המחסנית ריקה: isEmpty הוספה לראש המחסנית: push הסרה ראש המחסנית: pop קבלת הערך שבראש המחסנית: top עבור מחסנית שאינה מוגבלת בגדולה המימוש יהיה באמצעות רשימה מקושרת: הוספה תמיד תתבצע לראש הסרה תמיד תבוצע מהראש ניתן לממש מחסנית בגודל מוגבל ואז המימוש יוכל להיות באמצעות מערך. היתרון: חסכון בהקצאות הדינאמיות שיש ברשימה מקושרת
43
מימוש מחסנית ע"י מערך (1) #include <stdio.h>
מימוש מחסנית ע"י מערך (1) #include <stdio.h> #include <stdlib.h> typedef struct { int* arr; int phisicalSize; int logicalSize; } Stack; Stack makeStack(int maxElements) } Stack s = {NULL, maxElements, 0}; s.arr = (int*)calloc(maxElements, sizeof(int)); return s; void freeStack(Stack* s) free(s->arr); s->logicalSize = 0; s->phisicalSize = 0;
44
מימוש מחסנית ע"י מערך (2) int isFull(Stack* s) int isEmpty(Stack* s) {
return s->logicalSize == s->phisicalSize; void push(Stack* s, int newValue) if (!isFull(s)) s->arr[s->logicalSize++] = newValue; int top(Stack* s) } return s->arr[s->logicalSize-1]; // assumption: at lease one value is in the stack int pop(Stack* s) return s->arr[--(s->logicalSize)]; int isEmpty(Stack* s) { return s->logicalSize == 0;
45
מימוש מחסנית ע"י מערך (3) void main() { Stack s = makeStack(10);
int topValue; push(&s, 5); push(&s, 2); push(&s, 7); push(&s, 8); push(&s, 1); printf("The stack is: "); printStack(&s); topValue = top(&s); printf("The stack after top: "); topValue = pop(&s); freeStack(&s); void printStack(Stack* s) { int i; printf("%d values (out of max %d):\n", s->logicalSize, s->phisicalSize); for (i=s->logicalSize -1 ; i >= 0; i--) printf("%d ", s->arr[i]); printf("\n");
46
ביחידה זו למדנו: מהי רשימה מקושרת פונקציות של רשימות מקושרות מימושים רקורסיביים לפונקציות של רשימות מקושרות ההבדל בין מערך לרשימה וריאציות של רשימות מקושרות תור ומחסנית
47
תרגילים כתוב פונקציה המקבלת מצביע לרשימה מקושרת ומספר. הפונקציה תסיר מהרשימה את כל המופעים של המספר. כתוב פונקציה המקבלת רשימה מקושרת ושני מספרים. הפונקציה תוסיף את המספר השני לאחר כל מופע של המספר הראשון. כתוב פונקציה המקבלת רשימה מקושרת ומחזירה 1 אם הרשימה ממוינת מהערך הקטן לגדול, אחרת הפונקציה תחזיר 0. כתוב פונקציה המקבלת 2 רשימות מקושרות ממוינות (ניתן להניח כי הקלט אכן תקין). יש לייצר ולהחזיר רשימה חדשה (עם צמתים חדשים) המכילה רק מספרים הקיימים בשתי הרשימות. כתוב פונקציה המקבלת רשימה מקושרת, ומחזירה רשימה חדשה שמכילה הפניות רק לאיברים שערכם גבוה מהערך הממוצע של הרשימה. שימו לב, אין לייצר צמתים חדשים, אלא רק לשנות הצבעות.
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.