You are on page 1of 35

‫מבוא לתכנות מערכות‬

‫רשימות מקושרות‪ ,‬מחסניות‪ ,‬תורים‬

‫ש ק פ ים ‪ :‬ד ר ׳ ע פ ר ש י ר‪ ,‬ע וד כ ן ע ״ י א י ת י ש ר ו ן‬

‫החוג למדעי המחשב‬


‫נושאים להרצאה זו‬

‫• רשימה מקושרת )‪(Linked list‬‬


‫• מחסנית )‪(Stack‬‬
‫• תור )‪(Queue‬‬

‫‪2‬‬
‫רשימות מקושרות‬
‫‪Linked lists‬‬

‫‪3‬‬
‫רשימה מקושרת‪ :‬בסיס‬
‫• מבנה נתונים שאיבריו מסודרים באופן לינארי‬

‫• מימוש פשוט וגמיש לרשימה שמאפשר הוספה והסרה של‬


‫איברים מהאמצע בצורה פשוטה; לא בהכרח מימוש יעיל‪.‬‬

‫• איבר ברשימה מחזיק במידע‪ ,‬ובנוסף מחזיק מצביע‪/‬ים‬


‫המעידים על מיקומו‪:‬‬
‫– ברשימה חד‪-‬כיוונית‪ ,‬כל איבר יחזיק במצביע ל‪next-‬‬
‫– ברשימה דו‪-‬כיוונית כל איבר יחזיק במצביעים ל‪ prev-‬ול‪next-‬‬

‫‪4‬‬
‫רשימה מקושרת חד‪-‬כיוונית‬
‫‪data=17‬‬ ‫• רשימה מקושרת הינה שרשרת של משתנים‬
‫‪next=0xdff268‬‬ ‫בזיכרון כאשר כל משתנה מצביע למשתנה הבא‬
‫בשרשרת‬
‫‪data=200‬‬
‫– סוף הרשימה מיוצג ע"י מצביע ל‪NULL-‬‬
‫‪next=0xdef4c4‬‬
‫• רשימה מקושרת הינה מבנה נתונים המאפשר‬
‫‪data=-15‬‬
‫שמירת ערכים בזיכרון‬
‫‪next=0x43ba12‬‬
‫– דוגמא נוספת למבנה נתונים‪ :‬מערך‬

‫‪data=8‬‬
‫• רשימה מקושרת מאפשרת‪:‬‬
‫– שמירה של מספר ערכים שאינו קבוע‬
‫‪next=0x0‬‬
‫– הכנסה והוצאה של משתנים מכל מקום ברשימה‬
‫– חיבור וחלוקת רשימות‬

‫‪5‬‬
‫מערך לעומת רשימה מקושרת‬
‫רשימה מקושרת‬ ‫מע ר ך‬
‫שימוש בשטח זכרון שאינו‬ ‫מבנה נתונים המשתמש‬
‫בהכרח רציף‬ ‫בשטח זיכרון רציף‬
‫גישה מהירה לתאי המערך גישה איטית לאיברי הרשימה‬
‫)לכתיבה‪/‬קריאה( אם המערך לצורך קריאה‪/‬כתיבה )נדרש‬
‫מעבר על כל האיברים במקרה‬ ‫ממויין‬
‫הגרוע(‬
‫כדי להגדיל את המערך‪ ,‬יש הוספת‪/‬הורדת איברים הינה‬
‫לחפש מקום חדש ולהעתיק מהירה – נדרשת הקצאת מקום‬
‫לאיבר אחד נוסף‬ ‫את כל איבריו‪realloc :‬‬
‫פעולה יקרה!‬
‫‪6‬‬
‫מבנים המתייחסים לעצמם‬
typedef struct Bday_t {
char* name;
Date date;
struct Bday_t *next;
} Bday;

int main(void){
Bday dt1, dt2, *pdt;
set_day(dt1.date, 31); set_month(dt1.date, 12);
set_year(dt1.date, 1992);
set_day(dt2.date, 1); set_month(dt2.date, 1);
set_year(dt2.date, 1993);
pdt = &dt1;
dt1.next = &dt2;
dt2.next = NULL;
printf("Year in dt1: %d\nYear in dt2: %d\n",
get_year(dt1.date), get_year(dt1.next->date));
return 0;
} 7
‫ מבנים המתייחסים לעצמם‬:‫מימוש‬
:‫• נבחן את המבנה הבא‬
next; struct node {
typedef
int data;
} struct
*Node;node *next;
} Node;

?‫• איך נראים המבנים בזיכרון לאחר ביצוע הקוד הבא‬


Node* node1 = (Node*)malloc(sizeof(Node));
Node* node2 = (Node*)malloc(sizeof(Node));
node1->data = 1;
node1->next = node2;
node1 node2
node2->data = 2;
data=1 data=2
node2->next = NULL;
next=0xdff268 next=0x0

8
‫דוגמא‪ :‬שמירת ימי הולדת‬
‫• משימה‪ :‬כתבו תכנית המקבלת רשימת תאריכים‬
‫מהמשתמשת ומדפיסה אותם‬

‫‪newNode‬‬
‫• פתרון‪:‬‬
‫‪next=0x0ffef6‬‬

‫‪day=9‬‬
‫– ניצור רשימה מקושרת של תאריכים‬
‫– בכל פעם שנקלוט תאריך חדש נוסיפה‬
‫‪data‬‬

‫"‪month="JUN‬‬

‫‪year=1962‬‬ ‫לתחילת הרשימה‬

‫‪list‬‬
‫‪0x0ffef6‬‬ ‫‪next=0x0ffef6‬‬ ‫‪next=0x0ffef6‬‬

‫‪day=9‬‬ ‫‪day=9‬‬
‫‪data‬‬

‫‪data‬‬

‫"‪month="JUN‬‬ ‫"‪month="JUN‬‬

‫‪year=1962‬‬ ‫‪year=1962‬‬
‫‪9‬‬
‫פיתרון‬
#include <stdlib.h>
#include <string.h>
#include "Date.h"

typedef struct Bday_t {


char* name;
Date date;
struct Bday_t* next;
} Bday;

10
‫פיתרון‬
Bday* addNode(const char* name, Date d, Bday* head) {
Bday* ptr = (Bday*)malloc(sizeof(Bday));
if(!ptr)
return NULL;
ptr->name = strdup(name);
ptr->date = create_date();
set_day(ptr->date, get_day(d));
set_month(ptr->date, get_month(d));
set_year(ptr->date, get_year(d));
ptr->next = head;
return ptr;
}

11
‫פיתרון‬
Bday* remove_bday(Bday* head, Bday* node) {
if(head == NULL)
return NULL;
Bday *curr=head->next, *prev=head;
if(!strcmp(head->name, node->name)) {
free(head->name); destroy_date(head->date); free(head);
return curr;
}
while(curr && strcmp(node->name, curr->name)) {
prev=curr; curr=curr->next;
}
if(curr) {
prev->next = curr->next;
free(curr->name); destroy_date(curr->date); free(curr);
}
return head;
}
12
‫פיתרון‬
void destroyList(Bday* head) {
while(head) {
head = remove_node(head, head);
}
}

13
‫שימוש‬
int main(void) {
Bday *head = NULL, *ptr;
Date date;
char* name=NULL;
// Function birthdayRead is implemented elsewhere
while (birthdayRead(name, date)) {
head = addNode(name, date, head);
free(name);
date_destroy(date);
}
for(ptr=head ; ptr!=NULL ; ptr=ptr->next)
birthdayPrint(ptr); // Function birthdayPrint
// is implemented elsewhere...
destroyList(head);
return 0;
}
14
‫הערות‬

‫• רשימה מקושרת יכולה להיות גם דו‪-‬‬


‫כיוונית‪ .‬במקרה כזה לכל צומת שני‬
‫‪1‬‬ ‫‪2‬‬ ‫‪3‬‬ ‫‪4‬‬ ‫‪5‬‬
‫מצביעים‪ next :‬ו‪prev-‬‬
‫– יתרונה של רשימה מקושרת דו‪-‬כיוונית הוא‬
‫בסיבוכיות של תסריטים מסוימים ‪ -‬לכן‬
‫עדיף להתחיל בכתיבת רשימה חד‪-‬כיוונית‬
‫מאחר והיא פשוטה יותר‬
‫?‬ ‫‪2‬‬ ‫‪3‬‬ ‫‪4‬‬ ‫‪5‬‬
‫• לעיתים נוח להוסיף איבר דמה לתחילת‬
‫הרשימה כדי לצמצם את מקרי הקצה‬
‫שצריכים לטפל בהם בקוד‬

‫‪15‬‬
‫סיכום‪ :‬רשימה מקושרת‬
‫ניתן ליצור רשימות מקושרות ע"י הוספת מצביע למבנה‬ ‫•‬
‫בתוך אותו המבנה‬
‫רשימות מקושרות אינן מוגבלות בגודלן ומאפשרות‬ ‫•‬
‫הכנסה נוחה של איברים באמצע הרשימה‬
‫את האיברים ברשימה יש להקצות דינאמית ולוודא‬ ‫•‬
‫שישוחררו‬
‫במימוש רשימה מקושרת ניתן ואף מומלץ להוסיף איבר‬ ‫•‬
‫דמה בתחילתה‬

‫‪16‬‬
‫מחסניות‬
‫‪Stacks‬‬

‫‪17‬‬
‫מחסנית‪ :‬הגדרה‬
‫• מחסנית היא מבנה נתונים לינארי בו הוצאה והכנסה של‬
‫איברים יכולה להתרחש רק מראש המבנה‬

‫• עיקרון המחסנית‪:‬‬
‫האחרון שנכנס הוא הראשון לצאת‬

‫‪LIFO: Last In First Out‬‬

‫‪18‬‬
Last In First Out

E top
D top D D top
C top C C C
B top B B B B
A top A A A A A

19
‫מחסנית‪ :‬הגדרת פעולות‬

‫איתחול מחסנית ריקה‬ ‫•‬


‫מחיקת מחסנית‬ ‫•‬
‫הכנסת אלמנט לראש המחסנית )‪(push‬‬ ‫•‬
‫הוצאת אלמנט מראש המחסנית )‪(pop‬‬ ‫•‬
‫בדיקת הערך שבראש המחסנית‪ ,‬ללא הוצאה )‪(top‬‬ ‫•‬
‫שאילתא‪ :‬האם המחסנית ריקה?‬ ‫•‬

‫‪20‬‬
push

21
pop

22
‫מימוש מחסנית‬
‫איך נממש?‬

‫‪23‬‬
‫מימוש מחסנית‬
‫איך נממש?‬
‫• מערך עם מצביע לראש המחסנית‬

‫‪top‬‬

‫• רשימה מקושרת‬

‫‪top‬‬ ‫‪24‬‬
C ‫ממשק מחסנית בשפת‬
#ifndef STACK_H
#define STACK_H
#include <stdbool.h>
typedef void* Element;
typedef struct Stack* Stack;

Stack StackCraete(Element (*cpy)(Element),


void (*fre)(Element));
void StackDestroy(Stack);
void StackPush(Stack, Element);
Element StackPop(Stack);
Element StackTop(Stack);
bool StackIsEmpty(Stack);
#endif
25
‫תורים‬
Queues

26
‫תור‪ :‬הגדרה‬
‫• תור הוא מבנה נתונים לינארי בו הכנסת איברים‬
‫מתרחשת בקצה אחד והוצאת האיברים מתרחשת‬
‫מן הקצה השני‬
‫• בתור עצמו – המידע מעובד בסדר בו התקבל‬
‫• עיקרון התור‪ :‬הראשון שנכנס הוא הראשון לצאת‬

‫‪FIFO: First In First Out‬‬

‫‪27‬‬
First In First Out

enqueue enqueue enqueue dequeue enqueue dequeue dequeue dequeue

28
‫שימושים בתור‬
‫• אחד ממבני הנתונים היותר שימושיים לעיבוד מידע‬
‫במערכות הפעלה וניהול רשתות‬
‫– ניהול תור בקשות אל מול משאבים‬
‫– התאמה בין ייצור מהיר של מידע לצריכתו )מעבד‪/‬מדפסת(‬

‫• תורת התורים מעניקה כלים חישוביים חזקים ומהווה‬


‫בסיס תיאורטי יציב לניהול תורים‬

‫‪29‬‬
‫תור‪ :‬הגדרת פעולות‬

‫איתחול תור ריק‬ ‫•‬


‫מחיקת תור‬ ‫•‬
‫הכנסת אלמנט לסוף התור )‪(enqueue‬‬ ‫•‬
‫הוצאת אלמנט מראש התור )‪(dequeue‬‬ ‫•‬
‫שאילתא‪ :‬האם התור ריק?‬ ‫•‬

‫‪30‬‬
enqueue (push)

31
dequeue (pop)

32
‫מימוש תור‬
‫איך נממש?‬

‫‪33‬‬
‫מימוש תור‬
‫איך נממש?‬
‫• מערך עם מצביעים לראש ולסוף התור‬

‫‪front‬‬ ‫‪rear‬‬

‫• רשימה מקושרת )דו‪-‬כיוונית(‬

‫‪front‬‬ ‫‪rear‬‬ ‫‪34‬‬


C ‫ממשק תור בשפת‬

#ifndef QUEUE_H
#define QUEUE_H
#include <stdbool.h>
typedef void* Element;
typedef struct Queue* Queue;

Queue QueueCreate(Element (*cpy)(Element),


void (*fre)(Element));
void QueueDestroy(Queue);
void QueueEnqueue(Queue, Element);
Element QueueDequeue(Queue);
bool QueueIsEmpty(Queue);
#endif 35

You might also like