Heap Sort (מיון ערימה)
עודכן לאחרונה
מיון ערימה מתייחס למערך כאל ערימה בינארית. קודם הוא בונה ערימת מקסימום, כך שהאיבר הגדול ביותר יושב בשורש (אינדקס 0). אחר כך הוא מחליף שוב ושוב את השורש עם האיבר הלא ממוין האחרון, וכך נועל את המקסימום במקומו, ומוריד את השורש החדש כדי להחזיר את תכונת הערימה. לחצו על הפעלה למעלה כדי לראות את בניית הערימה ואת השליפות.
מיון ערימה מבטיח זמן של O(n log n) כמו מיון מיזוג, אבל ממיין במקום עם O(1) זיכרון נוסף בלבד. הוא לא יציב ובדרך כלל ההתנהגות שלו מול המטמון גרועה יותר משל quicksort, ולכן בוחרים בו לעתים קרובות כשגם חסם מובטח וגם זיכרון קבוע חשובים.
סיבוכיות זמן וזיכרון
| מקרה | סיבוכיות | הערות |
|---|---|---|
| המקרה הטוב | O(n log n) | בנייה ועוד n שליפות |
| המקרה הממוצע | O(n log n) | סדר אקראי |
| המקרה הגרוע | O(n log n) | מובטח |
| זיכרון | O(1) | במקום |
| יציב | לא | ההורדה משנה את הסדר של איברים שווים |
צעד אחר צעד
| צעד | מה קורה |
|---|---|
| 1 | בונים ערימת מקסימום מהמערך (הורדה החל מההורה האחרון). |
| 2 | מחליפים את השורש (המקסימום) עם האיבר האחרון בערימה. |
| 3 | מקטינים את הערימה באחד: התא האחרון הזה ממוין עכשיו. |
| 4 | מורידים את השורש החדש כדי להחזיר את תכונת ערימת המקסימום. |
| 5 | חוזרים על כך עד שנשאר בערימה איבר אחד. |
דוגמה מפורטת
מיון של [3, 1, 6, 5, 2, 4]. הקו | מסמן את הגבול בין הערימה המצטמקת לזנב הממוין:
| מעבר | מערך | פעולה |
|---|---|---|
| בניית ערימה | [6, 5, 4, 1, 2, 3] | מורידים החל מההורה האחרון כדי לבנות את ערימת המקסימום; 6 נמצא עכשיו בשורש. |
| 1 | [5, 3, 4, 1, 2 | 6] | מחליפים את השורש 6 עם התא האחרון, מקטינים את הערימה ומורידים את 3. |
| 2 | [4, 3, 2, 1 | 5, 6] | מוציאים את השורש 5, ואז מורידים את 2 כך ש-4 עולה לשורש. |
| 3 | [3, 1, 2 | 4, 5, 6] | מוציאים את השורש 4, ואז מורידים את 1 כך ש-3 עולה לשורש. |
| 4 | [2, 1 | 3, 4, 5, 6] | מוציאים את השורש 3; 2 כבר מקיים את תכונת הערימה. |
| 5 | [1 | 2, 3, 4, 5, 6] | מוציאים את השורש 2; נשאר איבר אחד, ולכן המערך ממוין. |
מתי להשתמש במיון ערימה
| השתמשו בו כאשר | הימנעו ממנו כאשר |
|---|---|
אתם צריכים מקרה גרוע מובטח של O(n log n) בלי סיכון ל-O(n²). | אתם צריכים מיון יציב ששומר על הסדר של מפתחות שווים. |
הזיכרון מוגבל: הוא ממיין במקום עם O(1) זיכרון נוסף בלבד. | ביצועי המטמון חשובים והנתונים נכנסים לזיכרון: quicksort בדרך כלל מהיר יותר. |
| אתם כבר מתחזקים ערימה (למשל תור עדיפויות) על הנתונים. | אתם רוצים הכי מעט השוואות: מיון מיזוג ו-quicksort מבצעים לעתים קרובות פחות בפועל. |
| קלט לא מהימן עלול להפעיל את המקרה הגרוע של quicksort ואי אפשר להכניס אקראיות. | הנתונים כמעט ממוינים: מיון הכנסה רץ עליהם בזמן כמעט ליניארי. |
קוד Heap Sort
מימוש נקי של Heap Sort שאפשר להריץ, ב-Python, JavaScript, Java, C++, C. בחרו שפה, העתיקו את הקוד, או פתחו אותו טעון מראש בעורך האונליין של Coddy.
קוד Heap Sort ב-Python
1def heap_sort(a):2 n = len(a)3 # Build a max-heap, deepest parent first4 for i in range(n // 2 - 1, -1, -1):5 sift_down(a, i, n)6 # Repeatedly move the max to the end and shrink the heap7 for end in range(n - 1, 0, -1):8 a[0], a[end] = a[end], a[0]9 sift_down(a, 0, end)10 return a11
12
13def sift_down(a, i, size):14 while True:15 largest = i16 left, right = 2 * i + 1, 2 * i + 217 if left < size and a[left] > a[largest]:18 largest = left19 if right < size and a[right] > a[largest]:20 largest = right21 if largest == i:22 return23 a[i], a[largest] = a[largest], a[i]24 i = largest25
26
27nums = [12, 11, 13, 5, 6, 7]28print("Before:", nums)29heap_sort(nums)30print("After: ", nums)קוד Heap Sort ב-JavaScript
1function heapSort(a) {2 const n = a.length;3 // Build a max-heap, then repeatedly move the max to the end4 for (let i = Math.floor(n / 2) - 1; i >= 0; i--) siftDown(a, i, n);5 for (let end = n - 1; end > 0; end--) {6 [a[0], a[end]] = [a[end], a[0]];7 siftDown(a, 0, end);8 }9 return a;10}11
12function siftDown(a, i, size) {13 while (true) {14 const left = 2 * i + 1;15 const right = 2 * i + 2;16 let largest = i;17 if (left < size && a[left] > a[largest]) largest = left;18 if (right < size && a[right] > a[largest]) largest = right;19 if (largest === i) return;20 [a[i], a[largest]] = [a[largest], a[i]];21 i = largest;22 }23}24
25const data = [5, 2, 9, 1, 7, 3];26console.log("Before:", data);27console.log("Sorted:", heapSort([...data]));קוד Heap Sort ב-Java
1import java.util.Arrays;2
3public class Main {4 static void heapSort(int[] arr) {5 int n = arr.length;6 // Build a max-heap, deepest parent first7 for (int i = n / 2 - 1; i >= 0; i--) siftDown(arr, i, n);8 // Repeatedly move the max to the end and shrink the heap9 for (int end = n - 1; end > 0; end--) {10 swap(arr, 0, end);11 siftDown(arr, 0, end);12 }13 }14
15 static void siftDown(int[] arr, int i, int size) {16 while (true) {17 int largest = i, l = 2 * i + 1, r = 2 * i + 2;18 if (l < size && arr[l] > arr[largest]) largest = l;19 if (r < size && arr[r] > arr[largest]) largest = r;20 if (largest == i) return;21 swap(arr, i, largest);22 i = largest;23 }24 }25
26 static void swap(int[] arr, int a, int b) {27 int tmp = arr[a];28 arr[a] = arr[b];29 arr[b] = tmp;30 }31
32 public static void main(String[] args) {33 int[] arr = {12, 11, 13, 5, 6, 7};34 System.out.println("Before: " + Arrays.toString(arr));35 heapSort(arr);36 System.out.println("After: " + Arrays.toString(arr));37 }38}קוד Heap Sort ב-C++
1#include <iostream>2#include <utility>3#include <vector>4
5void printVec(const std::vector<int>& a) {6 for (int x : a) std::cout << x << " ";7 std::cout << "\n";8}9
10void siftDown(std::vector<int>& a, int n, int i) {11 while (true) {12 int largest = i, l = 2 * i + 1, r = 2 * i + 2;13 if (l < n && a[l] > a[largest]) largest = l;14 if (r < n && a[r] > a[largest]) largest = r;15 if (largest == i) return;16 std::swap(a[i], a[largest]);17 i = largest;18 }19}20
21void heapSort(std::vector<int>& a) {22 int n = static_cast<int>(a.size());23 // Build a max-heap in place24 for (int i = n / 2 - 1; i >= 0; --i) siftDown(a, n, i);25 // Repeatedly move the max to the end and shrink the heap26 for (int end = n - 1; end > 0; --end) {27 std::swap(a[0], a[end]);28 siftDown(a, end, 0);29 }30}31
32int main() {33 std::vector<int> data = {12, 11, 13, 5, 6, 7};34 std::cout << "Before: ";35 printVec(data);36 heapSort(data);37 std::cout << "After: ";38 printVec(data);39 return 0;40}קוד Heap Sort ב-C
1#include <stdio.h>2
3void printArr(const int a[], int n) {4 for (int i = 0; i < n; i++) printf("%d ", a[i]);5 printf("\n");6}7
8void siftDown(int a[], int n, int i) {9 while (1) {10 int largest = i, l = 2 * i + 1, r = 2 * i + 2;11 if (l < n && a[l] > a[largest]) largest = l;12 if (r < n && a[r] > a[largest]) largest = r;13 if (largest == i) return;14 int tmp = a[i];15 a[i] = a[largest];16 a[largest] = tmp;17 i = largest;18 }19}20
21void heapSort(int a[], int n) {22 // Build a max-heap in place23 for (int i = n / 2 - 1; i >= 0; i--) siftDown(a, n, i);24 // Repeatedly move the max to the end and shrink the heap25 for (int end = n - 1; end > 0; end--) {26 int tmp = a[0];27 a[0] = a[end];28 a[end] = tmp;29 siftDown(a, end, 0);30 }31}32
33int main(void) {34 int data[] = {12, 11, 13, 5, 6, 7};35 int n = sizeof(data) / sizeof(data[0]);36 printf("Before: ");37 printArr(data, n);38 heapSort(data, n);39 printf("After: ");40 printArr(data, n);41 return 0;42}שאלות נפוצות על מיון ערימה
מהי סיבוכיות הזמן של מיון ערימה?
O(n log n) במקרה הטוב, הממוצע והגרוע. בניית הערימה היא O(n) וכל אחת מ-n השליפות עולה O(log n). הוא משתמש ב-O(1) זיכרון נוסף.האם מיון ערימה יציב?
מתי כדאי להשתמש במיון ערימה?
O(n log n) עם O(1) זיכרון נוסף בלבד. הוא נמנע מהסיכון של O(n²) ב-quicksort בלי החוצץ של O(n) במיון מיזוג, במחיר של יציבות וביצועי מטמון.מה ההבדל בין מיון ערימה ל-quicksort?
O(n²), ואילו מיון ערימה מבטיח O(n log n). בפועל quicksort בדרך כלל מהיר יותר בזכות מקומיות מטמון טובה יותר ופחות החלפות, ולכן מעדיפים את מיון הערימה בעיקר כשחייבים להבטיח את החסם של המקרה הגרוע.