Download presentation
Presentation is loading. Please wait.
1
הרצאה 06 רשימות מקושרות קרן כליף
2
ביחידה זו נלמד: מהי רשימה מקושרת פונקציות של רשימות מקושרות
מימושים רקורסיביים לפונקציות של רשימות מקושרות ההבדל בין מערך לרשימה וריאציות של רשימות מקושרות © Keren Kalif
3
מבנה נתונים מבנה נתונים הוא אוסף המאפשר לנו להחזיק יותר מאיבר אחד
למשל: מערך קיימים מבני נתונים נוספים שלכל אחד יש את היתרונות והחסרונות שלו, ואלו ישפיעו על בחירת שימוש המבנה נתונים אחד או אחר היתרונות של מערך: גישה ישירה לאיבר הוספה בקלות לסוף המערך החסרונות של מערך: הסרה/הוספה לאמצע מורכבת גודל מוגבל © Keren Kalif
4
רשימה מקושרת זהו מבנה נתונים המאפשר הוספה דינאמית של איברים לכל מקום באוסף בקלות אין צורך להחליט מראש כמה איברים יהיו ברשימה הרעיון הוא יצירת איבר וקישורו למקום המתאים ברשימה בכל רגע נתון כל איבר ברשימה מקושרת הוא מבנה המכיל נתון והצבעה לאיבר הבא האיבר האחרון ברשימה יצביע ל- NULL בניגוד למערך, האיברים אינם ברצף בזיכרון typedef int type; typedef struct LNode } type data; struct LNode* next; } LNode; 4 9 6 typedef struct List } struct LNode* head; } List; © Keren Kalif
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; } © Keren Kalif
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 © Keren Kalif
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 © Keren Kalif
8
פונקציה המשחררת את איברי הרשימה
void freeList(List* lst) { LNode* current = lst->head; LNode* next; while (current) next = current->next; free(current); current = next; } lst->head = NULL; current next lst head 1 ??? 2 2 © Keren Kalif
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); } © Keren Kalif
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 © Keren Kalif
11
פונקציה המאתחלת רשימה ריקה
פונקציה זו מאתחלת בשדות ה- head וה- tail את הערך NULL List makeEmptyList() { List lst; lst.head = lst.tail = NULL; return lst; } © Keren Kalif
12
פונקציה הבודקת האם רשימה ריקה
int isEmpty(const List* lst) { return lst->head == NULL; } © Keren Kalif
13
פונקציה שיוצרת איבר להכנסה לרשימה
הפונקציה תקבל את הערך שיהיה באיבר, ומצביע לאיבר הבא: פונקציה זו תשמש אותנו בפונקציות הבאות LNode* createNewNode(type newData, LNode* next) { LNode* newNode = (Node*)calloc(1], sizeof(LNode)); newNode->data = newData; newNode->next = next; return newNode; } © Keren Kalif
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; } newData = 4 3 2 lst head tail 4 newNode © Keren Kalif
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 © Keren Kalif
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 © Keren Kalif
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 © Keren Kalif
18
הוספת איבר לסוף הרשימה void insertNodeToTail(List* lst, LNode* newNode) { if (isEmpty(lst)) lst->head = lst->tail = newNode; else lst->tail->next = newNode; lst->tail = newNode; } © Keren Kalif
19
דוגמאת החברים © Keren Kalif
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); } © Keren Kalif
21
דוגמא: פיצול רשימה לזוגיים ואי-זוגיים (2)
דוגמא: פיצול רשימה לזוגיים ואי-זוגיים (2) 3 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; } 1 2 src head tail even head tail current next odd head tail פונקציה זו הורסת את הרשימה המקורית. כדי לשמור עליה היה מריך להעתיק קודם את רשימת המקור. © Keren Kalif
22
העתקת רשימה List copyList(const List* lst) {
List res = makeEmptyList(); LNode* temp = lst->head; while (temp) insertValueToTail(&res, temp->data); temp = temp->next; } return res; © Keren Kalif
23
הרעיון מאחורי הוספת ערך לאמצע רשימה
דוגמא: הוספת הערך 8 אחרי הערך 9 ראשית נייצר איבר חדש נחבר את האיבר החדש לאיבר שאמור להיות אחריו נחבר לאיבר החדש את האיבר שלפניו 4 9 6 8 © Keren Kalif
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 = (LNode*)malloc(sizeof(LNode)); newNode->data = newValue; newNode->next = temp->next; temp->next = newNode; הוספה לאחר איבר שלא קיים - לא תבוצע 2 9 6 insertAfter = 9 newValue = 8 head temp temp 8 newNode © Keren Kalif
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 © Keren Kalif
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 © Keren Kalif
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 © Keren Kalif
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); } © Keren Kalif
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); © Keren Kalif
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); } © Keren Kalif
31
מימושים רקורסיביים – אורך רשימה
int getListLengthRec(List* lst) { return getListLengthHelper(lst->head); } int getListLengthHelper(LNode* head) { if (head == NULL) return 0; return 1 + getListLengthHelper(head->next); } © Keren Kalif
32
דוגמאת ניהול הזכרון © Keren Kalif
33
וריאציות של רשימה מקושרת
עבודה עם dummy head: בעת הוספה/הסרה וכד' חוסך בדיקה האם האיבר הוא הראשון רשימה דו-כיוונית: יעיל עבור טיול דו כיווני, מאפשר לא לשמור את המשתנה prev בכל מקום בו צריך לשמור הפניה לאיבר הקודם (למשל במחיקה) מאפשר גישה מיידית לאיבר קודם מקל על פעולת החלפה של 2 איברים רשימה מעגלית האיבר האחרון מצביע לראשון © Keren Kalif
34
יתרונות/חסרונות לשימוש ברשימה מקושרת
יתרונות: ניתן להוסיף בקלות איברים לכל מקום באוסף ניתן להסיר איברים בקלות חסרונות: אין גישה ישירה לאיבר שימוש בהקצאה דינאמית עבור כל איבר חדש נשתמש כאשר הפעולות העיקריות על האוסף יהיו הוספה להתחלה, לאמצע, הסרה © Keren Kalif
35
ביחידה זו למדנו: מהי רשימה מקושרת פונקציות של רשימות מקושרות
מימושים רקורסיביים לפונקציות של רשימות מקושרות ההבדל בין מערך לרשימה וריאציות של רשימות מקושרות © Keren Kalif
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.