Download presentation
Presentation is loading. Please wait.
Published byΚυριακή Εφθαλία Βονόρτας Modified over 6 years ago
1
מערכים, מטריצות דלילות, ורשימות מקושרות
Lecture2 of Geiger & Itai’s slide brochure מערכים, מטריצות דלילות, ורשימות מקושרות חומר קריאה לשיעור זה Chapter 11.2– Linked lists (204 – 213) Geiger & Itai, 2001
2
מערך כטיפוס נתונים אבסטרקטי
מערך מוגדר ע"י הפעולות הבאות: create(type,I)– מחזיר מערך A של איברים מטיפוס type כאשר האינדקסים הם הקבוצה הסופית I. get(A,i) – מחזיר את הערך של האיבר עם אינדקס i I בתוך A. store(A,i,e) – מאחסן במערך A, תחת אינדקס i I, את ערך הביטוי e. I בשפת c האינדקסים הם הקבוצה I={0,…,n-1} אחזר A[i] (get) שמור A[i] = e (store) כללים הנשמרים ע"י הפעולות: כל הערכים במערך מאותו טיפוס, והוא נקבע בהכרזה (type). הערך המאוחזר לפי אינדקס i , הוא הערך האחרון שנשמר לפי אינדקס i. אחזור לפי אינדקס i מחזיר ערך בלתי מוגדר אם מעולם לא אוחסן ערך לפי אינדקס זה. cs,Technion
3
מימושים למערך (1) אזור זיכרון רצוף: כל הפעולות מתבצעות בזמן O(1).
מימושים למערך (1) address 10243 10244 10245 10246 10247 10248 10249 10250 10251 10252 10253 10254 10255 10256 10257 10258 10259 10260 10242 10241 10240 10261 10262 אזור זיכרון רצוף: כל הפעולות מתבצעות בזמן O(1). base 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 offset הפעולות: מתבססות על חישובי כתובות Base+offset cs,Technion
4
מימושים למערך (2) מימוש באמצעות מספר אזורים בזיכרון: לכל אזור גודל קבוע n. base k blocks n כדי לממש מערך בגודל m יש לבחור k=m/n. נקצה מערך עזר (base) בגודל k האיבר A[i] נמצא בכתובת base[ i/n ] + i%n. זמן חישוב הכתובת: O(1). דוגמא: n=6 הכתובת של A[16] היא: base[ 16/6 ] + 16%6 = base[2] + 4 cs,Technion
5
מערך דו-ממדי int a[m][n] כל שורה A[i] היא מערך חד-ממדי באורך n. v v v
A0,n-1 A0,1 A0,0 A1,n-1 A1,1 A1,0 v v v Am-1,n-1 Am-1,1 Am-1,0 נניח ש-base היא הכתובת של A[0][0] והמימוש הוא באזור זיכרון רצוף. הכתובת של שורה A[i] היא base + in הכתובת של איבר A[i][j] היא base + in + j cs,Technion
6
מערך רב-ממדי (1) i3n2 n1 + i2n1 + i1 גודל אורך מישור שורה
במערך דו-ממדי: int A[n2][n1] הכתובת של איבר A[i2][i1] מחושבת, כפי שראינו, ע"י הנוסחה: base + i2n1 + i1 במערך תלת-ממדי: int A[n3][n2][n1] הכתובת של איבר A[i3][i2][i1] מחושבת ע"י הנוסחה: base + i3n2 n1 + i2n1 + i1 i3 i1 n1 i2 n2 1 2 i3n2 n1 + i2n1 + i1 גודל מישור אורך שורה הערה: החישוב מניח קונוונצית ציינים ב-C (a[0] הוא המקום הראשון)
7
מערך רב-ממדי (2) במערך d-ממדי: int A[nd]…[n3][n2][n1]
הכתובת של A[id]…[i3][i2][i1] מחושבת ע"י הנוסחה: base + idnd-1 … n1 + id-1 nd-2 … n1 +…+ i1 כמה פעולות במקרה הגרוע? = base + ((… ((idnd-1+id-1)nd-2 + id-2)nd-3 + … + i3 ) n2+i2)n1 + i1 אבל אפשר לבטא את הכתובת באופן אלטרנטיבי: כמה פעולות במקרה הגרוע?
8
מערך רב-ממדי (3) הנוסחה לחישוב הכתובת במערך רב ממדי דומה לכלל הורנר לחישוב ערך פולינום: p(x0) = akxk + ak-1xk-1 + …+ a1x + a0 = (…(akx + ak-1)x +…+ a1)x+a0 התוכנית הבאה מחשבת את ערך הפולינום בנקודה x: p=a[k]; for (j=k-1; j>=0; j--) p = px + a[j] באופן אנלוגי, התוכנית הבאה מחשבת את הכתובת של A[id]…[i3][i2][i1] הנתונה ע"י: base + ((… ((idnd-1+id-1)nd-2 + id-2)nd-3 + … + i3 ) n2+i2)n1 + i1 addr=i[d]; for (j=d-1; j>=1; j--) addr = addr*n[j] + i[j]; addr= base + addr;
9
סריקת מערך רב-ממדי זמן סריקת כל איברי מערך A[nd]…[n3][n2][n1] תלוי בסדר הסריקה. סריקה לפי סדר האחסון מהירה יותר בגלל לוקליות של גישה לזיכרון. במימוש שהצגנו, סריקה כאשר האינדקסים הנמוכים נסרקים ראשונים מהירה יותר מסריקה בה האינדקסים הגבוהים נסרקים ראשונים. השיפור הוא בקבוע בלבד ואינו מתבטא בסימון O (בהנחה ש-ni הם קבועים) n1 n2
10
איתחול מערך שאלה: כמה עולה לאתחל מערך באורך n?
תשובה: איתחול נאיבי – O(n) נתאר מבנה נתונים לאיתחול מערך בזמן קבוע! הרעיון: לא באמת נאתחל את המערך. נשתמש בזיכרון נוסף לציין האם כבר נכתב ערך בתא במערך cs,Technion
11
נסיון לפתרון נשתמש במשתנה עזר top: יציין כמה איברים במערך "נכתבו"
נשתמש גם במערך עזר C: לכל i < top C[i] , מאחסן את האינדקס (ב-A) של האיבר ה-(i+1) שאותו כבר "כתבנו" המציינים ערכים המוגדרים 5 4 3 2 1 333 7 222 111 3 5 453 5 זבל 643 4 123 top 3 2 1 1 מציינים מוגדרים 4 A C
12
איתחול מערך בזמן O(1) דוגמא: המציינים מצביעים V
ערכים לאזור הבטוח המוגדרים A B C 212 131 111 222 7 333 1 2 3 4 5 333 7 222 111 3 212 333 7 222 111 3 5 131 224 554 675 234 1 2 3 4 5 234 675 554 1 2 234 675 554 1 131 4 1 123 643 453 4 1 101 123 643 453 4 177 101 123 643 453 1 2 3 5 מציינים מוגדרים top זבל top top לא O(1) ! כמה "יעלה" זמן גישה? בדוגמא זו: V[0]=5, V[1]=3, V[4]=7 cs,Technion
13
הקוד לאיתור זבל int is_garbage(int i) {
return !(( B[i] < top && (B[i] >= 0) && C[B[i]] = = i)); } דוגמא לאיבר שהוכנס למבנה: C[B[4]] = = 4 דוגמאות לזבל: C[B[5]] > top וכן C[B[3]]= = 0 המציינים מצביעים V ערכים לאזור הבטוח המוגדרים A B C 5 4 3 2 1 333 7 222 111 3 5 131 224 554 675 234 1 2 3 4 5 234 2 554 1 234 675 554 1 131 4 1 123 643 453 2 3 5 top מציינים מוגדרים זבל
14
הקוד לפעולות המערך top = 0; אתחל init(V,const): constant = const;
if (is_garbage(i)) return constant; else return A[i]; אחזר get(V,i): if (is_garbage(i)) { C[top] = i ; B[i] = top ; top = top +1;} A[i]= e; שמור store(V,i,e): הערה: ברגע שהמערך מתמלא(top > n) ניתן להשתמש במערך A בלבד כיוון שאין, ויותר לא יהיה, זבל ב-A.
15
ייצוג מטריצות סימטריות
מטריצה סימטרית היא מערך A דו-ממדי המקיים A[i,j]=A[j,i]. n 3 2 1 6 5 4 10 9 8 7 … הייצוג: אוסף וקטורים המאוחסנים במערך רציף יחיד: מהי הכתובת של איבר A[i,j] ? נשתמש בנוסחה: 1+2+…+i = i(i+1)/2 { Base + i ( i+1 )/2 + j if i j Addr(j,i) Otherwise חצי תחתון Addr(i,j)= חצי עליון
16
ייצוג מטריצות דלילות מטריצה דלילה היא מערך דו-ממדי עבורו "רוב מוחלט" של האיברים שווה לאפס (או לקבועc כלשהו).מהו רוב מוחלט ? 7 6 5 4 3 2 1 8 בדוגמא זו 7 מתוך 28 איברים שונים מהקבוע. הגדרה אסימפטוטית: אם סדרת מטריצות M1...Mn ... מקיימת שמספר האיברים השונים מקבוע c הוא מסדר גודל של o(m(n)) (o קטן) כאשר m(n) הוא מספר המקומות המקסימלי במטריצה Mn אז זוהי סדרה של מטריצות דלילות. דוגמא: מטריצות אלכסוניות. במטריצות ריבועיות דלילות מגודל nn מספר האיברים השונים מקבוע c הוא o(n2). ייצוג: "רשימת" שלשות(i,j,Aij) מסודרות בסדר לקסיקוגרפי. דוגמא: (0,2,3), (0,5,1), (1,1,5), (2,3,4), (4,1,2), (4,4,7), (4,5,8)
17
חסרונות ויתרונות של הייצוג
הייצוג: רשימת שלשות(i,j,Aij) מסודרות בסדר לקסיקוגרפי. חסרון עקרי: אין גישה אקראית לפי מציין בזמן O(1). יתרונות: חוסך בזיכרון עבור מטריצות דלילות. מאיץ פעולות חיבור וכפל של מטריצות דלילות. חיבור מטריצות בגודל n X n לוקח בייצוג רגיל זמן O(n2). נניח כעת שיש m איברים שונים מהקבוע c במטריצה אחת ו-k בשניה. חיבור שתי המטריצות נעשה ע"י מיזוג שתי הרשימות המייצגות את המטריצות נ"ל. זמן המיזוג הוא אורכן הכולל של הרשימות כלומר O(m+k). כלומר עבור מטריצות ריבועיות דלילות זמן החיבור הוא o(n2). תרגיל ממבחן: הראו שכפל מטריצות ריבועיות דלילות בייצוג זה לוקח זמן o(n3) במקום O(n3). לשם כך נדרש מיון לפי עמודות של מטריצה B.
18
תוכנית לחיבור מטריצות C=A+B
typedef struct NODE { int row, col; float val; } NODE A[mA+1], B[mB+1], C[mA+mB+1]; int lex(NODE a, NODE b); /* lexicographically compares a and b; Returns: if (a.row,a.col) < (b.row,b.col) 0 if (a.row,a.col) = (b.row,b.col) 1 if (a.row,a.col) > (b.row,b.col) */ void ADD(NODE *A, NODE *B, NODE *C, int *mC){ int i=0, j=0, *mc=0; A[mA]=B[mB]={+,+, 0}; while (i < mA || j < mB){ switch lex(A[i],B[j]){ case –1: C[*mC++] = A[i++]; break; case 1: C[*mC++] = B[j++]; break; case 0: C[*mC].row = A[i].row; C[*mC].col = A[i].col; C[*mC].val = A[i++].val + B[j++].val; if (C[*mC].val 0) *mC++; }}} cs,Technion
19
רשימות מקושרות נזכר כעת כיצד מתבצע חיפוש, הכנסה, והוצאה (find, insert, delete) ברשימות מקושרות ונגדיר וריאציות עליהן. 7 head 2 6 5 חסרונות בהשוואה למערך: יתרונות בהשוואה למערך: אין גישה לפי אינדקס בזמן O(1). מאפשר הקצאת זיכרון דינמית. אין צורך להקצות מקום זיכרון רצוף. ניתן להוציא איבר מתוך רשימה מקושרת בלי להשאיר "חור" כמו במערך ולכן ניתן לסרוק את כל האיברים בזמן ליניארי. cs,Technion
20
חיפוש ברשימה מקושרת פעולת איתחול init(head): פעולת חיפוש find(x,head):
void init (NODE *head){ (* head) = NULL; } פעולת איתחול init(head): NODE * find (DATA_TYPE x, NODE *head){ NODE *t; for (t = head; t != NULL && t -> info != x; t = t -> next ); return t; } פעולת חיפוש find(x,head): חיפוש איבר בזמן O(n) במקרה הגרוע. 7 head 2 5 t cs,Technion
21
הכנסת איבר לרשימה מקושרת
int insert ( NODE *t, DATA_TYPE x){ NODE *p; if (t == NULL) return 0; p = (NODE *) malloc (sizeof (NODE)); p -> info = x; p -> next = t -> next ; t -> next = p ; return 1; } פעולת insert(t,x): הפרמטר t מצביע לצומת שאחריו מוסיפים צומת חדש. הכנסת איבר בזמן O(1) כאשר ידוע מקום ההכנסה. דוגמא: insert(t,6). 7 head 2 6 5 t cs,Technion
22
הוצאת איבר מרשימה מקושרת
delete ( NODE *t){ NODE * temp ; temp = t next; /* מצביע לצומת שמורידים*/ t next = temp next ; free (temp) ; } פעולת delete(t): הפרמטר t מצביע לצומת שלפני הצומת שמוציאים. הוצאת איבר בזמן O(1) כאשר ידוע מקום ההוצאה. 7 head 2 6 5 t temp cs,Technion
23
הוצאת איבר מראש רשימה מקושרת
הוצאת איבר מראש רשימה מקושרת int delete_first ( NODE **p_head){ NODE * temp ; if (*p_head = = NULL) return 0 ; temp = *p_head; *p_head = temp next ; free (temp) ; return 1; /* success code */ } פעולת delete_first(r): 7 head 2 5 p_head תכנות זה מסורבל. מהו הפתרון לכך ? cs,Technion
24
רשימות עם כותרת מוסיפים איבר "ריק" בתחילת הרשימה. תוספת זו חוסכת:
טיפול מיוחד ברשימות ריקות טיפול מיוחד בזמן הכנסה לפני הצומת הראשון. בייצוג זה רשימה בת שתי איברים ממומשת כך: head 2 5 head ורשימה ריקה ממומשת כך: 5 head 1 2 הכנסה לפני איבר ראשון זהה להכנסה לפני איבר כלשהו: cs,Technion
25
רשימות מעגליות 2 5 rear 9 6 7 יתרונות: אפשר להגיע מכל איבר לכל איבר
ניתן לשרשר רשימות מעגליות בזמן קבוע. rear1 2 5 9 7 3 4 6 8 rear2 temp cs,Technion
26
רשימה מקושרת דו-כיוונית
2 5 8 9 t יתרון: מאפשר להוציא איבר בהינתן מצביע tאליו (ולא רק את האיבר שאחריו). t next prev = t prev ; t prev next = t next ; 8 9 5 2 t cs,Technion
27
הוצאה "טריקית" האם ניתן להוציא איבר אליו מצביע tגם מרשימה חד-כוונית ?
2 head 5 9 t 8 פשוט נעתיק את האינפורמציה בתא העוקב ל-t לתא ש-t מצביע אליו ונוציא את התא העוקב. 2 head 8 9 t מהן החסרונות של שיטה זו ? צומת יכול להכיל הרבה אינפורמציה ולכן העתקה עלולה להיות פעולה ארוכה. יתכן וישנם מצביעים נוספים לתא שהוצא ואין אפשרות לעדכן מצביעים אלה. cs,Technion
28
ייצוג פולינומים נעלם אחד. דוגמא: 0.3 9 4.5 2 0.1 מקדם חזקה
מקדם חזקה מימוש זה יעיל כאשר ההפרש בין החזקה הגבוהה והנמוכה גדול וכן הרבה מקדמים של חזקות הביניים מתאפסים. שני נעלמים. דוגמא: 2 9 1 5 3 14 7 cs,Technion
29
ייצוג מטריצות דלילות מבנה צומת: column row value next down
1 2 3 4 column row value next down typedef struct node { float value ; struct node*next, *down ; int row, column ; } NODE ; cs,Technion
30
כפל מטריצות דלילות כפל מטריצות C = AB. התוכנית מחשבת את האיבר Cij.
#define N_row 20 #define N_col 20 NODE *row_header[N_row], *col_header[N_col]; /* Compute the value of c[i][j] */ double mult_row_col(int i, int j){ double c = 0 ; NODE *r = row_header[i], *k = col_header[j]; while (r != NULL && k != NULL){ if (r column < k row) r = r next ; else (r column > k row) k = k down ; else /* r->column ==k->row */ c + = r value * k value ; r = r next ; k = k down ; } return c; } cs,Technion
Similar presentations
© 2025 SlidePlayer.com Inc.
All rights reserved.