סיכום הקורס מבוא למבני נתונים ואלגוריתמים 04468 ע"פ הרצאותיה של דר' ליאן לוית-איתן, חורף 010 הוכן ע"י איליה מלמד מבני נתונים בסיסיים: מחסנית תור יישום מחסנית בעזרת רשימה מקושרת: יישום,תור בעזרת מערך: )כל הפעולות לוקחות O( 1 )כל הפעולות לוקחות O( 1 מכניס איבר חדש (x) Push מכניס איבר חדש (x) Isert tmp ew LikedItem A[tail] x tmp.data x tail (tail+1) mod N tmp.ext top top tmp () Remove מוציא איבר x A[head] head (head+1) mod N retur x מוציא איבר () Pop if IsEmpty() stop tmp top top top.ext retur tmp.data מראה את האיבר בראש המחסנית () Top retur top.data האם המחסנית ריקה () IsEmpty if top==nil retur TRUE retur FALSE מיונים Top () x A[head] מראה את האיבר בראש התור האם התור ריק () IsEmpty if tail==head retur True retur FALSE האם התור מלא () IsFull if (tail+1) mod N ==head retur TRUE retur FALSE Radix Sort d k d k d k Bucket Sort Coutig Sort k k k Bubble Sort Isertio Sort Heap Sort log log log Merge Sort log log log Quick Sort log log Best Case Avg. Case Worst Case - 1 - - - מידע - נוסף זכרון נוסף הערות טווח איברי הקלט -האיברים מפוזרים אוניפורמית -טווח איברי הקלט -מס' הספרות -טווח הספרות k k 1 1 1 1 הכי מהיר מבחינת קבועים. מיון יציב מיון יציב מתאים רק כאשר האיברים מפוזרים אוניפורמית דורש מיון יציב עבור כל ספרה d -מס' הספרות k -טווח הספרות Radomized log
יישום מיונים: CoutigSort (A, B, k) for i 1 to k Cout[i] 0 for j 1 to legth[a] Cout[A[j]]++ for i to k Cout[i] Cout[i]+Cout[i-1] for j legth[a] dowto 1 B[Cout[A[j]] A[j] Cout[A[j]]-- HeapSort (A) BuildHeap (A) for 1 heapsize[a] dowto swap A[1] A[i] heapsize[a] heapsize[a]-1 Heapify (A, 1) QuickSort (A, left, right) if left < right p Partitio (A, left, right) QuickSort (A, left, p) QuickSort (A, p+1, right) Partitio (A, left, right) e A[left] L left-1 R right+1 while TRUE repeat R-- util A[R] e repeat L++ util A[L] e if L<R swap A[L] A[R] retur R Rad_QuickSort (A, left, right) if left < right p Rad_ Partitio (A, left, right) Rad_QuickSort (A, left, p) Rad_QuickSort (A, right, p) MergeSort (A, p, r) if p<r q p+r MergeSort (A, p, q) MergeSort (A, q+1, r) Merge (A, p, q, r) IsertioSort (A) for i to legth[a] key A[i] j i-1 while j>0 ad A[j]>key A[j+1] A[j] j j-1 A[j+1] key BubbleSort(A) for i 1 to legth[a]-1 cout 0 for j legth[a] dowto i+1 if A[j]<A[j-1] swap A[j] A[j-1] cout++ if cout == 0 stop RadixSort (A, d, ) for i 1 to d do a stable sort of A, acordig to digit i BucketSort (A) legth[a] for i 1to B A [i] m A[i] for i 1 to -1 *IsertioSort (B[i]) cocateate the lists {B[0]..B[m]} by order. *-other sorts ca be used Rad_Partitio (A, left, right) q Radom (left, right) swap A[left] A[q] retur Partitio (A, left, right) log חסם תחתון של בעיית המיון )ללא מידע נוסף(:
ערימה - Heap ערימה היא עץ בינארי כמעט מלא שבו לכל צומת מוגדר ערך "מפתח". ערך של מפתח גדול או שווה ל כל ערכי המפתחות של הבנים שלו. הערך בראש הערימה הוא תמיד האיבר הגדול ביותר במערך. ניתן להגדיר גם ערימת מינימום ע "י שינוי ההגדרה מגדול לקטן, ובכל המימושים ע "י שינוי הסימן ל-. בערמת מינימום האיבר בראש הערמה הוא הקטן ביותר.. O log תקינים. סיבוכיות i בהנחה שהוא אינו במקום. מניח שכל הצאצאים של במקומו, ממקם את האיבר ה- i - Heapify. O סיבוכיות.A יוצר ערמה תקנית ממערך - Build Heap O log מוציא את האיבר המקס' מהערימה ושומר על מבנה תקין שלה. סיבוכיות - Heap Extract Max O log מכניס איבר לערימה למקומו הנכון. סיבוכיות -Heap Isert O log ממיין את המערך ע"י שימוש בטור קדימויות. סיבוכיות - Priority Que Sort Paret (i) = i/ Left (i) = i Right (i) = i+1 Heapify (A, i) left Left(i) right Right(i) if left heap_size ad A[left] > A[i] largest left largest i if right heap_size ad A[left] > A[largest] largest right if largest i swap A[i] A[largest] Heapify (A, largest) BuildHeap (A) heap_size[a] legth [A] for i legth[a]/ dowto 1 heapify (A, i) HeapExtractMax (A) if heap_size[a] < 1 error "heap uderflow" max A[1] A[1] A[heap_size[A]] heap_size[a]-- Heapify (A, 1) retur max HeapIsert(A, key) heap_size[a]++ i heap_size[a] while i>0 ad A[Paret(i)] < key A[i] A[Paret(i)] i Paret(i) A[i] key PQSort (A) S Ø for i 1 to HeapIsert(S, A[i]) for i dowto 1 SortedA[i] HeapExtractMax(S) יישום של ערמה בעזרת מערך: בעיית הבחירה - Statistics Order - Rad Select פתרון רנדומלי בעיית הבחירה ב - O במקרה הממוצע ו- O במקרה הגרוע. - Select פתרון דטרמיניסטי שפותר את בעיית הבחירה ב- O בכל מקרה. *האלגוריתם לא נלמד בקורס, אך צריך לדעת כי הוא קיים RadSelect (A, p, r, i) if p==r retur A[p] q RadPartitio (A,p,r) k q-p+1 if (i==k) retur A[q] if i < k retur RadSelect (A, p, q-1, i) retur RadSelect (A, q+1, r, i-k) 3 Select (A, p, r, i) if (r-p+1) 5 sort (A[p..r]) retur A[i] r-p+1 Medias[1../5] A[(l+):5:(r-)] x=select (Medias, 1, 5, 10 ) A=Partitio (A, l, r, x) k= fid x i A[l..r] if i k retur Select (A, l, k, i) retur Select (A, k, r, i-k+1). Select (A, 1,, +1 ע"מ למצוא את החציון נריץ: )
IOrder_TreeWalk (x) If x NIL IOrder_TreeWalk (Left[x]) Prit (key[x]) IOrder_TreeWalk (Right[x]) מוצא את האיבר הבא בסדר (x) TreeSuccesor If Right[x] NIL Retur TreeMi(Righr[x]) y Paret[x] while y NIL ad x == Right[y] x y y Paret[y] retur y מכניס איבר לעץ (z TreeIsert,T) y NIL x root[t] while x NIL y x if key[z] < key[x] x Left[x] x Right[x] paret[z] y if y == NIL root[t] z if key[z] < key[y] Left[y] z Right[y] z TreeSearch (x, k) עצי חיפוש בינארי- BST עץ חיפוש בינארי הוא עץ שלכל צומת יש שני בנים. הבן השמאלי קטן מהאב והימני גדול ממנו. סיורי עץ - walks Tree הרצת I order תדפיס את העץ בצורה ממויינת, סיבוכיות של כל הסיורים O מחזיר מצביע למיקום של האיבר if x = NIL or k=key[x] retur x if k < key[x] retur TreeSearch (Left[x], k) retur TreeSearch (Right[x], k) PostOrder_TreeWalk (x) If x NIL PostOrder_TreeWalk (Left[x]) PostOrder_TreeWalk (Right[x]) Prit (key[x]) TreeMax (x) מחזיר את המקס ' while Right[x] NIL x Right[x] retur x TreeMi (x) מחזיר את המינ ' while Left[x] NIL x Left[x] retur x PreOrder_TreeWalk (x) If x NIL Prit (key[x]) PreOrder_TreeWalk (Left[x]) PreOrder_TreeWalk (Right[x]) TreeDelete (T,z) if Left[z] == NIL or Right[z] == NIL y z y TreeSuccesor (z) if Left[y] NIL x Left[y] x Right[y] if x NIL Paret[x] Paret[y] if Paret[y]==NIL root[t] x if y == Left[Paret[y]] Left[Paret[y]] x Right[Paret[y]] y if y z key [z] key[y] retur y זמן ריצה של כל האלגוריתמים הנ"ל O, h כאשר h הוא גובה העץ. אם העץ הוא עץ מאוזן )Balaced( אז גובה העץ שווה ל-.log תחזוקה שוטפת של עץ מאוזן תוודא כי כל פעולה תעלה רק O. log ארגון מחדש של BST שלא תוחזק תעלה O. log 4
14 7 5 0 1 טבלאות ערבול/גיבוב Hashig משמשות כאשר רוצים להחזיק פעולות כתיבה, מחיקה וחיפוש ב-, O 1 ובסיבוכיות מקום O. נסמן: - מס' המפתחות בטבלה m- מס' המקומות בטבלה -α= m מקדם העומס - h:{keys} [1..m] פונק' ערבול, לוקחת מפתח ונותנת לו ערך מתאים בטבלה. דרישות מפונ' ערבול "טובה": סיבוכיות זמן O 1 לא יוצרת קבוצות לאחר עירבול, מפזרת את המפתחות בצורה אוניפורמית בטבלת העירבול דוגמאות לפונק' ערבול:,h(k)=k mod m יש לבחור עבור m ערכים ראשוניים שלא קרובים לחזקה מדוייקת של. h(k)= ka mod 1 כאשר 1 A 0 קבוע. עובד בצורה יעילה עבור פתרונות עבור התנגשויות של פונ' ערבול: A= 5 1 Chaiig כל תא בטבלת העירבול הוא רשימה שבה מאוחסנים כל האיברים בעלי אותו מפתח ערבול. חיפוש ומחיקה יתבצעו ב- h, O h אורך הרשימה, במקרה הגרוע ביותר מקבלים O. הוספה תתבצע ב-. O 1 ניתן לממש את הרשימה בעזרת.BST הדבר ישפר את המקרה הגרוע ביותר ל-,O(log) אך זה מעלה את זמן ההוספה ל- O log ומגדיל את הסרבול. Ope Addressig כל מקום בטבלה מכיל איבר אחד,ואם פונק' הערבול עלתה על מקום תפוס,היא תמשיך לחפש עד שתמצא מקום פנוי.החיפוש של המקום הפנוי הבא צריך להיות תלוי רק במפתח,לכן מרחיבים את פונק' הערבול כדי שתהיה תלויה גם במספר החיפוש. שיטות לבדיקה: h(k,i)=(h'(k)+i) mod m Liear Probig h(k,i)=(h'(k)+c 1 i+c i ) mod m Quadratic Probig,h(k)=(h 1 (k)+ih (k)) mod m Double Hashig שיטה זאת היא היעילה ביותר. Rehashig כאשר מגיעים ל- α קרוב ל- 1 נרצה להגדיל את גודל הטבלה שלנו. נעשה זאת ע"י פעולת Rehashig אשר מעבירה את כל האיברים לטבלה גדולה יותר, וממקמת אותם במקומם החדש. כל פעם שעושים Rehashig יש צורך לשנות את פונק ' העירבול בהתאם. למרות שכל פעולת Rehashig לוקחת O, בגלל שהן נדירות, נשמרת סיבוכיות ממוצעת O 1 לכל פעולה. Uio Fid טיפוס נתונים מופשט שמקיים שלוש פעולות: Uio,x) (y מאחד בין שתי הקבוצות x ו- y לקבוצה אחת. FidSet(x) מחזיר את שם הקבוצה ש- x שייך אליה. x. יוצר קבוצה בעלת איבר יחיד, MakeSet(x) Weighted במקרה הגרוע. ניתן לשפר את האלגוריתם והמבנה נתונים ל- O לוקח Fid לוקחים O. 1 תמיד ו- MakeSet Uio,Uio מה שמוריד את הזמן של Fid ל-. O log ניתן גם לשפר את האלגוריתם של Fid כך שבזמן מעבר על הערכים האלגוריתם מבצע,Path Compretio הדבר מוריד את הזמן הממוצע של פעולת חיפוש ל- O, log לכל ערך מציאותי ניתן להגיד כי 5,log ולכן ניתן להגיד שכל פעולת Fid לוקחת בממוצע O. 1 5
גרפים E קשת סיווג גרפים: פשוט לא קיימות קשתות עצמיות או מקבילות,(u,v) (v,u) בניגוד לגרף מכוון ש בו אין חשיבות ל חילוף בין כניסה מכוון גרף שבו יש חשיבות למוצא והכניסה של קשת ליציאה.(v,u)=(u,v). E V -1 קשיר ניתן להגיע מכל צומת לכל צומת אחר בגרף,. E V -1 יער אוסף של עצים, עצים: שלושה תנאים, שאם שנים מהם מתקיימים, השלישי גם: גרף קשיר חסר מעגלים E = V -1 רכיבי קשירות: רכיב קשירות תת גרף שכל הצמתים שלו מחוברים. רכיב קשיר היטב רכיב שניתן להגיע מכל צומת שלו לכל צומת אחר, רלוונטי רק לגרפים מכוונים. )כל צומת בפני עצמו הוא רכיב קשיר היטב( ייצוג של גרפים: רשימת סמיכויות Adjacecy List לכל אחד מהצמתים בגרף יש רשימה מקושרת שמכילה את כל הקשתות שלו. מטריצת סמיכויות Adjacecy Matrix כל תא במטריצה הוא 1 אם הקשת בין שני הצמתים בעלי האינדקסים המתאימים קיימת, 0 אחרת. מטריצה של גרף לא מכוון תהיה סימטרית 1 3 4 1 3 4 1 4 1 1 1 0 0 3 3 1 1 1 1 3 3 0 1 1 1 4 4 4 0 1 1 1 סיווג קשתות )לאחר הרצת חיפוש(: ) קשת עץ קשת שנכללת בתוך העץ. ( קשת קדמית קשת שיוצאת מאב קדמון ומגיעה לצאצא. ( קשת אחורית קשת שיוצאת מצאצא ומגיעה לאב קדמון. ( ) קשת cross כל קשת אחרת. ( *בגרף בלתי מכוון קשת קדמית היא גם קשת אחורית ) ) חיפוש לרוחב BFS-Breadth First Search החיפוש בוחן בשיטתיות את כל הצמתים שיוצאים מצומת המוצא S, ורק לאחר שסיים את כל הצמתים שמחוברים ישירות ל- S עובר לרמה הבאה. מחשב את המרחק קצר ביותר של כל צומת לצומת המקור. בדיקת קשירות: לאחר הרצה יחידה של BFS מצומת כלשהו, סיבוכיות של. O E + V :BFS V צומת BFS (G, s) Umark all vertices visit (s) Equeue (Q,s) BFS_Tree Ø while Q Ø v Dequeue (Q) for every w i Adj[v] if w ot visited visit (w) Eque (Q, w) BFS_Tree BFS_Tree {(v,w)} 1 BFS_ShortestPaths (G,s) for each vv d[v] d[s] 0 Equeue (Q,s) while Q Ø v Dequeue (Q) for each w i Adj[v] if d[w]== d[w] d[v]+1 Equeue (Q,w) לבדוק האם קיימים צמתים בלתי מסומנים; אם קיימים, לא קשיר. לאחר הרצה של BFS לא יהיו קשתות קדמיות או קשתות cross בין צמתים שהפרש המרחקים שלהם מצומת השורש גדול מ-. 6
חיפוש לעומק DFS-Depth First Search החיפוש הולך "לעומק" עד שהוא לא גומר לבדוק את כל הקשתות במסלול מסוים שיוצא מ- S הוא לא נסוג. בחיפוש זה מסמנים )לפי הצורך( זמן גילוי וסיום לכל צומת. סיבוכיות של :DFS 7 O E + V מיון קשתות לפי זמני גילוי וסיום: לקשת (v,u) קשת עץ: f[v]=f[u]+1 d[v]+1=d[u] or אחרת קשת קדמית: f[v]>f[u] d[v]<d[u] ad אחרת קשת אחורית: f[v]<f[u] d[v]>d[u] ad אחרת.cross קשת לאחר הרצת DFS קיימות קשתות cross קיימים מעגלים בגרף מיון טופולוגי מיון של הצמתים כך שכל צומת תלוי רק בצמתים שלפניו ולא אחריו. תנאי הכרחי ומספיק לקיום מיון טופולוגי: יהי G גרף קשיר חסר מעגלים קיים מיון טופולוגי לגרף G. מיון טופולוגי הוא לא בהכרח יחיד. אלגוריתם למיון טופולוגי על בסיס.Time Stampig סיבוכיות: O E + V גרפים ממושקלים גרפים שבהם לכל קשת יש משקל שונה. משקל יכול להיות שלילי. גרף לא ממושקל לכל הקשתות יש אותו משקל עץ פורש מינימאלי MST מוגדר רק עבור גרפים לא מכוונים קשירים. אם לא קיימות קשתות בעלות משקל שווה אז קיים MST יחיד. MST הוא עץ אשר מחבר בין כל הצמתים בגרף וסכום המשקלים של הקשתות שלו מינימאלי. ללמה: יהי,G=(E,V) ee X, V קשת בעלת משקל מינימאלי שמחברת את X ו-{ V/{X, אזי e תהיה בחלק מה- MST. אם לא קיימות קשתות נוספות בעלות משקל זהה שמחברות בין X ו-{ V/{X, אזי e תופיע בכל.MST שני אלגוריתמים למציאת,MST יכולים לתת תוצאות שונות אם קיימים כמה.MST GraphDFS (G) Umark all vertices for each vv if v ot marked DFS (G, v) DFS (G, s) visit (s) for each vadj[s] if v ot marked DFS (G, v) DFS_Tree DFS_Tree {(s,v)} TopologicalSort (G) for each vv umark (v) for each vv if v is umarked TopSort (G, v) DFS_TimeStampig (G) time 0 for each vv d[v] usee for each vv if d[v] == usee DFS(G,v) DFS(G, s) d[s] time++ visit (s) for each vadj[s] if d[v] == usee DFS(G, v) f[s] time++ TopSort (G, s) visit (s) for each vadj[s] if v is umarked TopSort (G,v) add s to frot of TopSort list Kruskal ממיין את כל הקשתות לפי משקל, כל פעם מוסיף את הקשת בעלת המשקל המינימאלי אם שני הצמתים שהיא מחברת אינם באותו רכיב קשירות. סיבוכיות: O E log E = O E log V
Prim מתחיל מצומת אקראי s ובכל פעם מוסיף את הקשת המינימאלית שמחברת צומת חדש לרכיב הקשירות של s. סיבוכיות: O V + E log V = O E log V MST_Kruskal (G, wt) T (V, Ø) for each vv[g] MakeSet (v) sort E by o-decreasig wt for each (u,v) E[G] (i sorted order) if FidSet(u) FidSet(v) T T {(u,v)} Uio (FidSet(u), FidSet(v)) retur T MST_Prim (G, wt) Q V[G] with cost[u] for all u choose radom start vertex s DecreaseKey (s,0) while Q Ø u ExtractMi(Q) if u s T T {(u,closest[u])} for each vadj[u] if vq ad wt (u,v)<cost[v] closest[v] u DecreaseKey (v, wt(u, v)) retur T מסלולים קצרים ביותר ממסלול יחיד SSSP אלגוריתם BFS פותר בעיה זו עבור גרפים שאינם משוקללים. נרצה למצוא פתרון גם עבור גרפים משוקללים. SSSP קיים רק אם לא קיימים מעגלים שליליים )שסך המשקל שלהם שלילי( בגרף. בוחר כל פעם בצומת בעלת המשקל הנמוך ביותר, ולא משנה יותר אלגוריתם Dijkstra האלגוריתם הטוב ביותר עבור גרפים ללא משקלים שליליים. מסלולים. סיבוכיות O V + E log V = O E log V אלגוריתם Belma-Ford יודע לעבוד עם גרפים בעלי משקל שלילי. אם מפעילים אינטרציה נוספת בסוף הריצה שתעבור על כל הקשתות ותבדוק אם יש שינויים נוכל לאתר מעגלים שליליים. סיבוכיות O VE SSSP_Dijkstra (G, s, wt) S Ø d[s] 0 for all v i V[G]-s d[v] π[v] NIL Q V while Q Ø u ExtractMi (Q) s S {u} for each v(adj[u]-s) if d[v] > d[u]+wt(u,v) d[v] d[u]+wt(u,v) DecreaseKey (v, d[v]) π[v] u SSSP_Belma-Ford (G, s, wt) for m 0 to V -1 d m d m [s] 0 π 0 for m 1 to V -1 D m d m-1 for all (u,v) E if d m [v] > d m-1 [u]+wt(u,v) d m [v] d m-1 [u]+wt(u,v) π[v] u output: d v -1, π אלגוריתם למציאת רכיבי קשירות חזקים בגרף מכוון) SCC (: u. לכל )f[u]( זמני סיום לחישוב קריאה ל-( DFS(G.G T חשב את.f[u] אבל בחירת הצמתים לפי סדר יורד של DFS(G T קריאה ל-( בפעם השניה כ- SCC. DFS הוצא את הצמתים של כל עץ מהרצת e u, v G e v, u G T :G הוא גרף שבו כיווני כל הקשתות הפוך לכיווניהן ב- G T 8
T = T שיטות לחישוב נוסחאות רקורסיה נוסחת רקורסיה: נוסחה המתארת סיבוכיות זמן של אלגוריתם רקורסיבי, לדוגמא: + O שיטת חזרה דוגמא: :)Iteratio( לפתוח את הנוסחא ע"מ למצוא ביטוי מתמטי ניתן לחישוב. T T / T( / 4) / T / 8 / / 4... log 1 1... T / 11/ 1/ 4... 1/ 1 i i0 שיטת הצבה :)Substitutio( טובה במקרה ואנו יודעים איך הפתרון יראה. ניחוש צורת הפתרון הכללית שימוש באינדוקציה מתמטית למציאת הקבועים והוכחת נכונות הפתרון. דוגמא: :T = O log ננחש פתרון מהצורה,T = T + T c / log / c log / c log c log c log c c log, c 1 יש לוודא כי הקבוע לא משתנה וכי אי השיווין מתקיים, לדוגמא: T, = c אבל + 1 c T = 3 3 c 3 3 3 c 3 חשוב: אי קיום של שיטת ההצבה לא מהווה הוכחה לחוסר נכונות, זאת. לדוגמא: ייתכן כי,T()=O(3) אבל בשיטת ההצבה לא נצליח להוכיח שיטת עץ רקורסיה: שימושי במיוחד עבור אלגוריתמי "הפרד ומשול". בונים עץ רקורסיה )ע"פ חלוקת הרקורסיה(.בכל צומת רושמים את סיבוכיות הפעולות ללא סיבוכיות תתי הרקורסיות. לבסוף T = T 3 + T 3 סוכמים את הסיבוכיות של כל הצמתים, זאת הסיבוכיות של כל הרקורסיה.דוגמא: + O 3 i =1 i c 3 c c 3 c c = 3 i= log3 c 4 9 c 9 c 9 T = O log c 9 c ) b ו- b שיטת ה- Master : עבור נוסחאות מהצורה:, T = a T + f )זהה עבור b T = Θ log b a f = O log b a ε אזי: א. קיים > 0 ε קבוע כך ש: T = Θ log b a log אזי f = Θ log b a ב. c>1 a f b c f f = Ω log b a+ε ג. קיים > 0 ε קבוע כך ש : T = Θ f וגם עבור קבוע וכל- גדול מספיק אזי : 9