Merge Sort (מיון מיזוג)
עודכן לאחרונה
מיון מיזוג הוא אלגוריתם מסוג הפרד ומשול. הוא מפצל את המערך לחצאים באופן רקורסיבי עד שבכל חלק יש איבר אחד (שממוין מאליו), ואז ממזג את החלקים בחזרה לפי הסדר. שלב המיזוג עובר על שני תת מערכים ממוינים עם שני מצביעים, ובכל פעם מעתיק את האיבר הקטן מבין שני האיברים הקדמיים. לחצו על הפעלה למעלה כדי לראות את המערך נבנה מחדש מיזוג אחר מיזוג.
מכיוון שהוא תמיד מפצל לחצאים, מיון מיזוג רץ בזמן O(n log n) בכל מקרה: המקרה הגרוע שלו טוב כמו המקרה הטוב. המחיר הוא O(n) זיכרון נוסף עבור חוצץ המיזוג הזמני.
סיבוכיות זמן וזיכרון
| מקרה | סיבוכיות | הערות |
|---|---|---|
| המקרה הטוב | O(n log n) | תמיד חוצה את הקלט |
| המקרה הממוצע | O(n log n) | סדר אקראי |
| המקרה הגרוע | O(n log n) | מובטח: אין קלטים רעים |
| זיכרון | O(n) | חוצץ זמני למיזוג |
| יציב | כן | במיזוג, שוויון מוכרע לטובת הצד השמאלי |
צעד אחר צעד
| צעד | מה קורה |
|---|---|
| 1 | אם בטווח יש 0 או 1 איברים, הוא כבר ממוין. |
| 2 | מפצלים את הטווח לשני חצאים. |
| 3 | ממיינים ברקורסיה את החצי השמאלי במיון מיזוג. |
| 4 | ממיינים ברקורסיה את החצי הימני במיון מיזוג. |
| 5 | ממזגים את שני החצאים הממוינים עם שני מצביעים. |
דוגמה מפורטת
מיון של [5, 2, 4, 1]:
| שלב | מערך | פעולה |
|---|---|---|
| פיצול | [5, 2] | [4, 1] | מחלקים את המערך לשני חצאים |
| פיצול | [5] [2] | [4] [1] | מחלקים שוב עד שכל חלק הוא איבר יחיד |
| מיזוג | [2, 5] | [1, 4] | ממזגים את [5],[2] ל-[2, 5] ואת [4],[1] ל-[1, 4] |
| מיזוג | [1, 2, 4, 5] | ממזגים את [2, 5] ו-[1, 4]: בוחרים 1, אחר כך 2, אחר כך 4, ואז 5 |
| סיום | [1, 2, 4, 5] | המערך ממוין לגמרי |
מתי להשתמש במיון מיזוג
| השתמשו בו כאשר | הימנעו ממנו כאשר |
|---|---|
אתם צריכים מקרה גרוע מובטח של O(n log n) | הזיכרון מוגבל ו-O(n) זיכרון נוסף אינו מקובל |
| יציבות חשובה (מפתחות שווים שומרים על הסדר שלהם) | אתם ממיינים מערכים קטנים שבהם מיון הכנסה מהיר יותר |
| אתם ממיינים רשימה מקושרת | מיון במקום הוא דרישה מחייבת |
| הנתונים גדולים מדי לזיכרון RAM (מיון חיצוני) | מקומיות המטמון קובעת, והמעברים במקום של quicksort מנצחים |
קוד Merge Sort
מימוש נקי של Merge Sort שאפשר להריץ, ב-Python, JavaScript, Java, C++, C, Pseudocode. בחרו שפה, העתיקו את הקוד, או פתחו אותו טעון מראש בעורך האונליין של Coddy.
קוד Merge Sort ב-Python
1def merge_sort(a):2 if len(a) <= 1:3 return a4 mid = len(a) // 25 left = merge_sort(a[:mid])6 right = merge_sort(a[mid:])7 return merge(left, right)8
9
10def merge(left, right):11 out = []12 i = j = 013 while i < len(left) and j < len(right):14 if left[i] <= right[j]:15 out.append(left[i])16 i += 117 else:18 out.append(right[j])19 j += 120 out.extend(left[i:])21 out.extend(right[j:])22 return out23
24
25nums = [38, 27, 43, 3, 9, 82, 10]26print("Before:", nums)27print("After: ", merge_sort(nums))קוד Merge Sort ב-JavaScript
1function mergeSort(arr) {2 if (arr.length <= 1) return arr;3 const mid = Math.floor(arr.length / 2);4 const left = mergeSort(arr.slice(0, mid));5 const right = mergeSort(arr.slice(mid));6 return merge(left, right);7}8
9// Merge two sorted arrays into one sorted array10function merge(left, right) {11 const out = [];12 let i = 0;13 let j = 0;14 while (i < left.length && j < right.length) {15 out.push(left[i] <= right[j] ? left[i++] : right[j++]);16 }17 return out.concat(left.slice(i), right.slice(j));18}19
20const data = [5, 2, 9, 1, 7, 3];21console.log("Before:", data);22console.log("Sorted:", mergeSort(data));קוד Merge Sort ב-Java
1import java.util.Arrays;2
3public class Main {4 static void mergeSort(int[] arr, int left, int right) {5 if (left >= right) return;6 int mid = (left + right) / 2;7 mergeSort(arr, left, mid);8 mergeSort(arr, mid + 1, right);9 merge(arr, left, mid, right);10 }11
12 // Merge two sorted halves into a temp array, then copy back13 static void merge(int[] arr, int left, int mid, int right) {14 int[] tmp = new int[right - left + 1];15 int i = left, j = mid + 1, k = 0;16 while (i <= mid && j <= right) {17 tmp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];18 }19 while (i <= mid) tmp[k++] = arr[i++];20 while (j <= right) tmp[k++] = arr[j++];21 System.arraycopy(tmp, 0, arr, left, tmp.length);22 }23
24 public static void main(String[] args) {25 int[] arr = {38, 27, 43, 3, 9, 82, 10};26 System.out.println("Before: " + Arrays.toString(arr));27 mergeSort(arr, 0, arr.length - 1);28 System.out.println("After: " + Arrays.toString(arr));29 }30}קוד Merge Sort ב-C++
1#include <iostream>2#include <vector>3
4void printVec(const std::vector<int>& a) {5 for (int x : a) std::cout << x << " ";6 std::cout << "\n";7}8
9void merge(std::vector<int>& a, int lo, int mid, int hi) {10 std::vector<int> tmp;11 tmp.reserve(hi - lo + 1);12 int i = lo, j = mid + 1;13 while (i <= mid && j <= hi) {14 if (a[i] <= a[j]) tmp.push_back(a[i++]);15 else tmp.push_back(a[j++]);16 }17 while (i <= mid) tmp.push_back(a[i++]);18 while (j <= hi) tmp.push_back(a[j++]);19 for (size_t k = 0; k < tmp.size(); ++k) a[lo + k] = tmp[k];20}21
22void mergeSort(std::vector<int>& a, int lo, int hi) {23 if (lo >= hi) return;24 int mid = lo + (hi - lo) / 2;25 mergeSort(a, lo, mid); // sort the left half26 mergeSort(a, mid + 1, hi); // sort the right half27 merge(a, lo, mid, hi); // merge the sorted halves28}29
30int main() {31 std::vector<int> data = {38, 27, 43, 3, 9, 82, 10};32 std::cout << "Before: ";33 printVec(data);34 mergeSort(data, 0, static_cast<int>(data.size()) - 1);35 std::cout << "After: ";36 printVec(data);37 return 0;38}קוד Merge Sort ב-C
1#include <stdio.h>2#include <stdlib.h>3
4void printArr(const int a[], int n) {5 for (int i = 0; i < n; i++) printf("%d ", a[i]);6 printf("\n");7}8
9void merge(int a[], int lo, int mid, int hi) {10 int* tmp = malloc((hi - lo + 1) * sizeof(int));11 int i = lo, j = mid + 1, k = 0;12 while (i <= mid && j <= hi) {13 if (a[i] <= a[j]) tmp[k++] = a[i++];14 else tmp[k++] = a[j++];15 }16 while (i <= mid) tmp[k++] = a[i++];17 while (j <= hi) tmp[k++] = a[j++];18 for (k = 0; k <= hi - lo; k++) a[lo + k] = tmp[k];19 free(tmp);20}21
22void mergeSort(int a[], int lo, int hi) {23 if (lo >= hi) return;24 int mid = lo + (hi - lo) / 2;25 mergeSort(a, lo, mid); // sort the left half26 mergeSort(a, mid + 1, hi); // sort the right half27 merge(a, lo, mid, hi); // merge the sorted halves28}29
30int main(void) {31 int data[] = {38, 27, 43, 3, 9, 82, 10};32 int n = sizeof(data) / sizeof(data[0]);33 printf("Before: ");34 printArr(data, n);35 mergeSort(data, 0, n - 1);36 printf("After: ");37 printArr(data, n);38 return 0;39}קוד Merge Sort ב-Pseudocode
1DECLARE nums : ARRAY[1:7] OF INTEGER2DECLARE n : INTEGER3n ← 74nums[1] ← 75nums[2] ← 36nums[3] ← 97nums[4] ← 18nums[5] ← 59nums[6] ← 810nums[7] ← 211DECLARE temp : ARRAY[1:7] OF INTEGER12DECLARE i : INTEGER13
14PROCEDURE merge(lo : INTEGER, mid : INTEGER, hi : INTEGER)15 DECLARE a : INTEGER16 DECLARE b : INTEGER17 DECLARE k : INTEGER18 a ← lo19 b ← mid + 120 k ← lo21 WHILE a <= mid AND b <= hi DO22 IF nums[a] <= nums[b] THEN23 temp[k] ← nums[a]24 a ← a + 125 ELSE26 temp[k] ← nums[b]27 b ← b + 128 ENDIF29 k ← k + 130 ENDWHILE31 WHILE a <= mid DO32 temp[k] ← nums[a]33 a ← a + 134 k ← k + 135 ENDWHILE36 WHILE b <= hi DO37 temp[k] ← nums[b]38 b ← b + 139 k ← k + 140 ENDWHILE41 FOR k ← lo TO hi42 nums[k] ← temp[k]43 NEXT k44ENDPROCEDURE45
46PROCEDURE mergeSort(lo : INTEGER, hi : INTEGER)47 DECLARE mid : INTEGER48 IF lo < hi THEN49 mid ← (lo + hi) DIV 250 // Sort each half, then merge them51 CALL mergeSort(lo, mid)52 CALL mergeSort(mid + 1, hi)53 CALL merge(lo, mid, hi)54 ENDIF55ENDPROCEDURE56
57CALL mergeSort(1, n)58
59FOR i ← 1 TO n60 OUTPUT nums[i]61NEXT iשאלות נפוצות על מיון מיזוג
מהי סיבוכיות הזמן של מיון מיזוג?
O(n log n) במקרה הטוב, הממוצע והגרוע, כי הוא תמיד מחלק את המערך לחצאים. הוא משתמש ב-O(n) זיכרון נוסף עבור חוצץ המיזוג.האם מיון מיזוג יציב?
למה לבחור במיון מיזוג ולא ב-quicksort?
O(n log n) גם על קלטים עוינים והוא יציב, ואילו quicksort יכול להידרדר ל-O(n²). מיון מיזוג מועדף גם עבור רשימות מקושרות ומיון חיצוני. החיסרון הוא O(n) הזיכרון הנוסף שלו.מה ההבדל בין מיון מיזוג ל-quicksort?
O(log n) זיכרון מחסנית, ואילו מיון מיזוג מפצל לחצאים בלי להסתכל על הערכים וממזג בעזרת חוצץ של O(n). בפועל quicksort בדרך כלל מהיר יותר בזכות מקומיות המטמון, אבל למיון מיזוג יש מקרה גרוע מובטח של O(n log n) והוא יציב.מתי כדאי להשתמש במיון מיזוג בפועל?
O(n log n), כשממיינים רשימות מקושרות (שם הוא לא צריך גישה אקראית), או כשמבצעים מיון חיצוני של נתונים גדולים מכדי להיכנס לזיכרון. הימנעו ממנו כשהזיכרון מצומצם, כי הוא צריך O(n) זיכרון נוסף.האם מיון מיזוג ממיין במקום?
O(n) כדי למזג את שני החצאים, ולכן הוא לא ממיין במקום. קיימות גרסאות מיזוג במקום, אבל הן מורכבות, ואיטיות יותר או מאבדות את היציבות, ולכן הגרסה מבוססת החוצץ היא הבחירה הנפוצה.