Presentation is loading. Please wait.

Presentation is loading. Please wait.

הרצאה 06 רשימות מקושרות קרן כליף.

Similar presentations


Presentation on theme: "הרצאה 06 רשימות מקושרות קרן כליף."— Presentation transcript:

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


Download ppt "הרצאה 06 רשימות מקושרות קרן כליף."

Similar presentations


Ads by Google