Sorting II: הפרד ומשול
Last week: in-place sorting Bubble Sort – O(n 2 ) comparisons –O(n) best case comparisons, O(n 2 ) exchanges Selection Sort - O(n 2 ) comparisons –O(n 2 ) best case comparisons –O(n) exchanges (always) Insertion Sort – O(n 2 ) comparisons –O(n) best case comparisons –Fewer exchanges than bubble sort –Best in practice for small lists (<30)
This week Mergesort –O(n log n) always –O(n) storage Quick sort –O(n log n) average, O(n^2) worst –Good in practice (>30), O(log n) storage
MergeSort A divide-and-conquer technique Each unsorted collection is split into 2 –Then again Then again –Then again »……. Until we have collections of size 1 »Now we merge sorted collections –Then again Then again –Then again Until we merge the two halves
MS(N) Merge sort analysis = N = N/2 + N/2 MS(N/2) + N/4 N/4 = 4*N/4 + N/8 N/8 N/8 N/8 + = 8*N/8 Each level contributes N...
MS(N) Merge sort analysis MS(N/2) N/4 N/4 N/8 N/8 N/8 N/8 N/2 K = 1 N = 2 K lg N = K log n levels * n per level= O( nlog(n) ) K levels … N/2 K
MergeSort(array a, indexes low, high) 1.If (low < high) 2. middle (low + high) /2 3. MergeSort(a,low,middle) // split 1 4. MergeSort(a,middle+1,high) // split 2 5. Merge(a,low,middle,high) // merge 1+2
Merge(arrays a, index low, mid, high) 1.b empty array, t mid+1, i low, tl low 2.while (tl<=mid AND t<=high) 3. if (a[tl]<=a[t]) 4. b[i] a[tl] 5. i i+1, tl tl+1 6. else 7. b[i] a[t] 8. i i+1, t t+1 9.if tl<=mid copy a[tl…mid] into b[i…] 10.else if t<=high copy a[t…high] into b[i…] 11.copy b[low…high] onto a[low…high]
דוגמא Initial: Split: Merge: Merge: Merge:
The complexity of MergeSort Every split, we half the collection How many times can this be done? We are looking for x, where 2 x = n x = log 2 n So there are a total of log n splits
The complexity of MergeSort Each merge is of what run-time? First merge step: n/2 merges of 2 n Second merge step: n/4 merges of 4 n Third merge step: n/8 merges of 8 n …. How many merge steps? Same as splits log n Total: n log n steps
Storage complexity of MergeSort Every merge, we need to hold the merged array:
Storage complexity of MergeSort So we need temporary storage for merging –Which is the same size as the two collections together To merge the last two sub-arrays (each size n/2) We need n/2+n/2 = n temporary storage Total: O(n) storage
QuickSort Key idea: Select a item (called the pivot) Put it into its proper FINAL position Make sure: –All greater item are on one side (side 1) –All smaller item are on other side (side 2) Repeat for side 1 Repeat for side 2
Short example Let’s select 25 as our initial pivot. We move items such that: –All left of 25 are smaller –All right of 25 are larger –As a result 25 is now in its final position
Now, repeat (recursively) for left and right sides –Sort 12 –Sort needs no sorting For the other side, we repeat the process –Select a pivot item (let’s take 57) –Move items around such that left items are smaller, etc.
Changes into And now we repeat the process for left And for the right
QuickSort(array a; index low, hi) 1.if (low >= hi) 2. return ; // a[low..hi] is sorted 3.pivot find_pivot(a,low,hi) 4.p_index=partition(a,low,high,pivot) 5.QuickSort(a,low,p_index-1) 6.QuickSort(a,p_index+1,hi)
Key questions How do we select an item (FindPivot())? If we always select the largest item as the pivot –Then this process becomes Selection Sort –Which is O(n 2 ) So this works only if we select items “in the middle” –Since then we will have log n divisions How do we move items around efficiently (Partition()?) This offsets the benefit of partitioning
FindPivot To find a real median (middle item) takes O(n) In practice however, we want this to be O(1) So we approximate: –Take the first item (a[low]) as the pivot –Take the median of {a[low],a[hi],a[(low+hi)/2]} FindPivot(array a; index low, high) 1.return a[low]
Partition (in O(n)) Key idea: Keep two indexes into the array –up points at lowest item >= pivot –down points at highest item <= pivot We move up, down in the array Whenever they point inconsistently, interchange At end: up and down meet in location of pivot
partition(array a; index low,hi; pivot; index pivot_i) 1.down low, up hi 2.while(down<up) 3. while (a[down]<=pivot && down<hi) 4. down down while (a[hi]>pivot) 6. up up – 1 7. if (down < up) 8. swap(a[down],a[up]) 9.a[pivot_i]=a[up] 10.a[up] = pivot 11.return up
Example: partition() with pivot=25 First pass through loop on line 2: down up
Example: partition() with pivot=25 First pass through loop on line 2: down up We go into loop in line 3 (while a[down]<=pivot)
Example: partition() with pivot=25 First pass through loop on line 2: down up We go into loop in line 5 (while a[up]>pivot)
Example: partition() with pivot=25 First pass through loop on line 2: down up We go into loop in line 5 (while a[up]>pivot)
Example: partition() with pivot=25 First pass through loop on line 2: down up Now we found an inconsistency!
Example: partition() with pivot=25 First pass through loop on line 2: down up So we swap a[down] with a[up]
Example: partition() with pivot=25 Second pass through loop on line 2: down up
Example: partition() with pivot=25 Second pass through loop on line 2: down up Move down again (increasing) – loop on line 3
Example: partition() with pivot=25 Second pass through loop on line 2: down up Now we begin to move up again – loop on line 5
Example: partition() with pivot=25 Second pass through loop on line 2: down up Again – loop on line 5
Example: partition() with pivot=25 Second pass through loop on line 2: down up down < up? No. So we don’t swap.
Example: partition() with pivot=25 Second pass through loop on line 2: down up Instead, we are done. Just put pivot in place.
Example: partition() with pivot=25 Second pass through loop on line 2: down up Instead, we are done. Just put pivot in place. (swap it with a[up] – for us a[low] was the pivot)
Example: partition() with pivot=25 Second pass through loop on line 2: down up Now we return 2 as the new pivot index
Notes We need the initial pivot_index in partition() For instance, change FindPivot(): –return pivot (a[low]), as well as initial pivot_index (low) –Then use pivot_index in the final swap QuickSort: Average O(n log n), Worst case O(n 2 ) –works very well in practice (collections >30) –Average O(n log n), Worst case O(n 2 ) –Space requirements O(log n) – for recursion
סבוכיות: ממוצע (O(nlogn גרוע ביותר (O(n 2 מיון של האיברים [A[j],…., A[i במקום שיטה: 1) בחר פיבוט, V (אחד מערכי [A[j],…..,A[i) 2) מצא (וארגן) נקודה k כך ש: הערכים הקטנים מ- V מופיעים ב- [A[j],…..,A[k והגדולים שווים ל- V מופיעים ב- [A[k+1],…..,A[i 3) מיין את [A[j],…..,A[k ו- [A[k+1],…..,A[i רקורסיבית דוגמא לפיבוט: האיבר הגדול ביותר בין השניים השונים זה מזה משמאל QUICKSORT
האלגוריתם מורכב מ: 1) מציאת פיבוט FindPivot 2) חלוקה Partition 3) רקורסיה לך משמאל לימין ומצא את הגדול הראשון מבין השניים השתמש בשני סמנים L, R. R מתחיל מימין וזז שמאלה L מתחיל משמאל וזז ימינה 1. הסע R שמאלה כל עוד הערכים גדולים שווים מ Pivot 2. הסע L ימינה כל עוד הערכים קטנים מ Pivot 3. אם L>R עצור. אחרת: החלף בין האיברים של L ו- R הזז R שמאלה הזז L ימינה חזור ל 1. (R = L - 1) בדיקה !!!!
ב) QuickSort- פרט לקריאה הרקורסיבית, הכל לינארי במספר האלמנטים זמן כולל: סכם על כל האלמנטים את מספר הפעמים שמשתתפים בקריאה = סכום העומקים של האלמנטים (כולם) ניתוח סיבוכיות: א) נראה ש (Partition(i, j לוקחת (O(j - i התחשבנות עם כל איבר - לכל איבר מתבצע: א) הגעה עם המצביע ב) אחד מהשניים: 1) ממשיכים עם מצביע- (O(1 2) 1. ממתינים 2. מחליפים (O(1 3. מקדמים מצביע - לא חוזרים לאיבר מסקנה: סיבוכיות (Partition(i, j היא (O(j - i + 1
ג) Worst Case אם כל פעם נבחר לפיבוט את הגדול ביותר אז המערך בגודל n יתחלק למערך של 1 + מערך של n -1 n n - 1 n n - 1 סכום העבודה בכל הרמות (עבודה לינארית באלמנטים) = סכום העומקים = n ( …… + n - 1) = O(n 2 ) =
ד) ממוצע הנחה 1: כל האלמנטים שונים זה מזה (אלמנטים זהים רק מקטינים סיבוכיות) הנחה 2: כל הסדרים של [A[i],…,A[j שווי הסתברות! נכון?!? בעד: אי אפשר להבחין בין האלמנטים כי כולם היו גדולים שווים לפיבוט הקודם נאמץ את ההנחה! נגד: בניתוח מדוקדק- הפיבוט הקודם בד”כ בצד ימין של המערך הימני בכל זאת: - לא כל כך משמעותי (איבר בודד) - אם כן משמעותי: ערבב האיברים בצורה אקראית
סימון: (T(n זמן מיון מערך בגודל n. הסתברויות: ראשון הוא i + 1st שני קטן מ i + 1 החלף בין בני הזוג
נניח : נוכיח ( באינדוקציה ): אם C=4C 2
QuickSort- המהיר ביותר בממוצע מכל אלגוריתמי ה nlogn זרוז האלגוריתם: נוכל לדאוג שהפיבוט יהיה מרכזי יותר. דוגמה: האמצעי מבין שלושה אלמנטים דוגמה: קח k אלמנטים ובחר אמצעי TradeOff : k קטן חוסר איזון k גדול מציאת אמצעי יקרה החלטה על סיום האלגוריתם: - אנחנו סיימנו ב n = 2 - עבור n קטן לא אכפת לנו שהסיבוכיות תהיה (O(n 2 - Knuth ממליץ על n = 9 שימוש בפוינטרים לרשומות: - חוסך העתקות - עולה במצביעים
תור עדיפויות / ערימה מוטיבציה סטודנט מקבל במשך הסמסטר משימות מסוגים שונים ובתאריכים שונים. הרשימה מתעדכנת כל יום, כאשר מתקבלות משימות חדשות. סוגי המשימות הם תרגילי בית להגשה, תרגילי בית חופשיים מהגשה ומבחנים. לכל אחת מהמשימות יש תאריך יעד מוגדר, שלא תלוי ביתר המשימות. הסטודנט רוצה לערוך לעצמו רשימה מסודרת של עדיפויות לעבודות, כך שידע כל פעם את איזה משימה כדאי לו לבצע עכשיו כדי לעמוד בזמנים של כל המשימות.
דרישות ממבנה הנתונים מציאה מהירה של ראש סדר העדיפויות הכנסה יעילה של איבר (משימה) חדש ארגון המבנה באופן יעיל כאשר מוציאים את ראש סדר העדיפויות מהמבנה (לאחר מהמשימה בוצעה)
פעולות של מבנה הנתונים "ערימה" צור ערימה ריקה (make_heap(Q)) הכנס רשומה בעלת מפתח x לערימה ) insert (Q,x)) הדפס את הרשומה בעלת המפתח הגדול ביותר בערימה (max(Q)) הוצא את הרשומה בעלת המפתח הגדול ביותר בערימה(del_max(Q)) הדפס את כל הרשומות בסדר יורד (heap_sort(Q))
הגדרה: עץ בינארי שלם עץ בינארי הוא עץ, שלכל צומת שלו יש לכל היותר שני בנים, ולכל צומת מלבד השורש יש בדיוק אבא אחד. עץ בינארי מלא הוא עץ שבו לכל צומת מלבד העלים יש בדיוק 2 בנים. עץ בינארי שלם הוא עץ בינארי מלא שבו כל העלים הם בעלי אותו עומק. עץ בינארי כמעט שלם הוא עץ בינארי שלם שהוצאו ממנו עלים "מימין".
דוגמאות לעצים בינאריים עץ בינארי עץ בינארי כמעט שלםעץ בינארי מלא עץ בינארי שלם
מימוש ערימה בעזרת עץ בינארי כמעט שלם החוק הבסיסי של ערימת מקסימום: המפתח של כל הורה הוא גדול או שווה למפתחות של בניו הרשומה בעלת המפתח הגדול ביותר יושבת תמיד בשורש העץ
דוגמא לערימת מקסימום כל מסלול בעץ היוצא מהשורש הוא מסלול לא עולה השורש הוא המפתח המקסימלי בעץ
כל מערך יכול לייצג עץ בינארי כמעט מלא כל איבר במערך מייצג צומת בעץ לכל איבר i במערך, בניו בעץ יושבים במקומות 2i ו – 2i+1 במערך. אביו של האיבר ה - i במערך הוא האיבר ה – [i/2] שורש העץ הוא האיבר הראשון במערך עלי העץ הם האיברים שבניהם לא נכללים בתחום המערך
דוגמא להשמת עץ בינארי כמעט שלם בתוך מערך כל מערך יכול לייצג עץ בינארי כמעט שלם, וכל עץ בינארי כמעט שלם יכול להיות מיוצג בעזרת מערך
פונקצית עזר למימוש פעולות הערימה חוקיות הערימה מחייבת שהשורש תמיד יכיל את הרשומה בעלת המפתח המקסימלי בעץ. אחת הפעולות המותרות למבנה הנתונים ערימה הוא הוצאת האיבר המקסימלי מהערימה. דרושה טכניקה יעילה שתחזיר את הערימה למצב תקין
פעולת Shift_down(v) קלט: ערימה שהשורש שלה לא מקיים את תכונת הערימה פלט: ערימה תקנית פעולת Shift_down(v) 1.אם v עלה, או v גדול משני בניו, סיים 2.החלף את ערכו של v עם הערך של הגדול משני בניו (בסימון w) 3.חזור על הפעולה עם הערימה ששורשה הוא w.
דוגמא לפעולת Shift_down קלט : עץ בו השורש לא מקיים את כללי הערימה
דוגמא לפעולת Shift_down
דוגמא לפעולת Shift_down
פלט : ערימה תקינה
בניית ערימה ממערך נתון כלשהו A Make_heap(A) 1.עבור על כל איברי המערך מימין לשמאל 2.עבור כל אחד מהם בצע Shift_down.
דוגמא ליצירת ערימה קלט : מערך כלשהו
דוגמא ליצירת ערימה - המשך
דוגמא ליצירת ערימה - המשך
דוגמא ליצירת ערימה - המשך
דוגמא ליצירת ערימה - המשך
דוגמא ליצירת ערימה - המשך
דוגמא ליצירת ערימה - המשך
דוגמא ליצירת ערימה - המשך
דוגמא ליצירת ערימה - המשך
דוגמא ליצירת ערימה - המשך
דוגמא ליצירת ערימה - המשך
דוגמא ליצירת ערימה - המשך
דוגמא ליצירת ערימה - המשך
דוגמא ליצירת ערימה - המשך
דוגמא ליצירת ערימה - המשך
דוגמא ליצירת ערימה - המשך פלט : ערימת מקסימום
נכונות פעולת make_heap צריך להוכיח כי בסיום הפעולה יש בידינו ערימה. נתבונן בצומת כלשהו i. צריך להוכיח שערכו גדול או שווה לערכי בניו. בשלב המתאים באלגוריתם ביצענו עליו shift down, ומכאן שבזמן זה ערכו היה גדול או שווה לערכי בניו. בפעמים הבאות ששינינו את ערכו היה זה תוך כדי פעולת shift down שעברה דרכו, ופעולה זו כמובן שומרת אף היא על קיום תכונת הערימה בצומת i מכיוון שצומת i הוא שרירותי, הרי שמתקבלת ערימה.
זמן ריצה של בניית ערימה זמן ריצה של בניית ערימה הוא סכום גבהי כל הצמתים בעץ: משפט: סכום הגבהים של כל הצמתים בעץ בינארי שלם בעל n צמתים ובגובה H קטן מ – n. הוכחת המשפט: קודם כל יש לשים לב שעבור עץ בינארי שלם מתקיים
המשך הוכחת המשפט ישנו צומת בודד בגובה H (השורש), שני צמתים בגובה H-1 (בניו של השורש), 4 צמתים בגובה H-2,..., ובאופן כללי יש צמתים בגובה H-i. לפיכך נסכום ונקבל: מש " ל
מימוש הפעולות max(Q) ו – del_max(Q) פעולת max(Q): 1.הדפס את שורש העץ (האיבר הראשון במערך) –זמן הפעולה O(1) פעולת del_max(Q): 1.העבר את העלה הימני ביותר בעץ (האיבר האחרון במערך) למקום השורש r (המקום הראשון במערך) 2.בצע shift_down(r) –זמן הפעולה O(log n) –שימו לב שפעולה זו מחזירה ערימה תקינה אחרי הוצאת השורש מהערימה המקורית
מיון Heap_sort(A) Heap_Sort(A) 1.Q=Make_heap(A) 2.For i=1 to |A| do 3. print max(Q) 4. del_max(Q) הערה : במימוש ע " י מערך נהוג בפעולת del_max(Q) לא " לזרוק " את האיבר הגדול ביותר, אלא להחליפו עם האיבר האחרון במערך ואז לבצע shift_down. בצורה זו מתקבל מערך ממוין. כמובן שמחשיבים כ " ערימה " רק את האיברים המקוריים ללא תוספת הגדולים מימין.
דוגמא מלאה ל – Heap_sort(A) A = קלט : מערך A כלשהו
דוגמא - המשך Q = הערימה
דוגמא - המשך Q = ביטול הערך 63 על ידי החלפתו עם העלה הימני ביותר בעץ
דוגמא - המשך Q = Shift_down(8)
דוגמא - המשך Q = Shift_down(8)
דוגמא - המשך Q = Del_max(Q)
דוגמא - המשך Q = Shift_dowb(4)
דוגמא - המשך Q = Shift_dowb(4)
דוגמא - המשך Q = Del_max(Q)
דוגמא - המשך Q = Shift_down(6)
דוגמא - המשך Q = Shift_down(6)
דוגמא - המשך Q = Del_max(Q)
דוגמא - המשך Q = Shift_down(5)
דוגמא - המשך Q = Shift_down(5)
דוגמא - המשך Q = Del_max(Q)
דוגמא - המשך Q = Shift_down(6)
דוגמא - המשך Q = Shift_down(6)
דוגמא - המשך Q = Del_max(Q)
דוגמא - המשך Q = Shift_down(5)
דוגמא - המשך Q = Shift_down(5)
דוגמא - המשך Q = Del_max(Q)
דוגמא - המשך Q = Shift_down(4)
דוגמא - המשך Q = Del_max(Q)
דוגמא - המשך Q = Shift_down(5)
דוגמא - המשך Q = Del_max(Q)
דוגמא - המשך Q = Shift_down(4)
דוגמא - המשך Q = Del_max(Q)
דוגמא - המשך Q = Shift_down(4)
דוגמא - המשך Q = Del_max(Q)
בסוף האלגוריתם: המערך ממוין Q =
אנליזת מקום וזמן של Heap_sort(A) אנליזת מקום: כל הפעולות מתרחשות בתוך המערך שגודלו n, עם תוספות מקום של O(1), לכן המקום הוא O(n). אנליזת זמן: בניית ערימה O(n) בתוספת n פעולות של del_max(Q), לכן זמן הריצה עבור מיון זה:
מיון BucketSort מיון n מספרים שונים בתחום 1-k נשתמש במערך בוליני A באורך k , 5, 7, 1, 9, אפס את A. 2. לכל x בקלט בצע :A[x] = 1 3. עבור על המערך ואסוף :for (i = 1; i < k ; i++) { if (A[i] = = 1 ) output i ; }
1. אפס את A. 2. לכל x בקלט בצע :A[x] = 1 3. עבור על המערך ואסוף :for (i = 1; i < k ; i++) { if (A[i] = = 1 ) output i ; } סיבוכיות מיון BucketSort מיון n מספרים שונים בתחום 1-k אם סבוכיות זמן סתירה לחסם התחתון!
מיון BucketSort מיון n מספרים שאינם בהכרח שונים בתחום 1-k 3, 5, 3, 1, 9, 5, 3 3 1, 5 1, 3 2, 1 1, 9 1, 5 2, 3 3 מיון יציב Stable sort 1 1, 3 1, 3 2, 3 3, 5 1, 5 2, 9 1 נשתמש במערך A באורך k של תורים סבוכיות זמן
מיון BucketSort סבוכיות זמן אם סבוכיות זמן אם סבוכיות זמן שיטת BucketSort אינה יעילה כאשר n המספרים לקוחים מתחום " רחב מדי " 1..k, כלומר כאשר n << k
מיון RadixSort Least Significant Digits
Radix-Sort(A,d) for i 1 to d do use BucketSort to sort array A on digit i מיון RadixSort סבוכיות זמן RadixSort מיון מספרים אם בסיס RadixSort ממין מספרים
דוגמה סבוכיות זמן אם תחום המספרים סבוכיות זמן מיון BucketSort מיון RadixSort בסיס סבוכיות זמן