歸併排序

 

概要

本章介紹排序算法中的歸併排序。內容包括:
1. 歸併排序介紹
2. 歸併排序圖文說明
3. 歸併排序的時間複雜度和穩定性
4. 歸併排序實現
4.1  歸併排序C實現
4.2  歸併排序C++實現
4.3  歸併排序Java實現html

轉載請註明出處:http://www.cnblogs.com/skywang12345/p/3602369.htmljava


更多排序和算法請參考:數據結構與算法系列 目錄ios

 

歸併排序介紹

將兩個的有序數列合併成一個有序數列,咱們稱之爲"歸併"。
歸併排序(Merge Sort)就是利用歸併思想對數列進行排序。根據具體的實現,歸併排序包括"從上往下"和"從下往上"2種方式。算法


1. 從下往上的歸併排序:將待排序的數列分紅若干個長度爲1的子數列,而後將這些數列兩兩合併;獲得若干個長度爲2的有序數列,再將這些數列兩兩合併;獲得若干個長度爲4的有序數列,再將它們兩兩合併;直接合併成一個數列爲止。這樣就獲得了咱們想要的排序結果。(參考下面的圖片)數組

2. 從上往下的歸併排序:它與"從下往上"在排序上是反方向的。它基本包括3步:
① 分解 -- 將當前區間一分爲二,即求分裂點 mid = (low + high)/2;
② 求解 -- 遞歸地對兩個子區間a[low...mid] 和 a[mid+1...high]進行歸併排序。遞歸的終結條件是子區間長度爲1。
③ 合併 -- 將已排序的兩個子區間a[low...mid]和 a[mid+1...high]歸併爲一個有序的區間a[low...high]。數據結構

 

下面的圖片很清晰的反映了"從下往上"和"從上往下"的歸併排序的區別。ide

 

歸併排序圖文說明

歸併排序(從上往下)代碼spa

/*
 * 將一個數組中的兩個相鄰有序區間合併成一個
 *
 * 參數說明:
 *     a -- 包含兩個有序區間的數組
 *     start -- 第1個有序區間的起始地址。
 *     mid   -- 第1個有序區間的結束地址。也是第2個有序區間的起始地址。
 *     end   -- 第2個有序區間的結束地址。
 */
void merge(int a[], int start, int mid, int end)
{
    int *tmp = (int *)malloc((end-start+1)*sizeof(int));    // tmp是彙總2個有序區的臨時區域
    int i = start;            // 第1個有序區的索引
    int j = mid + 1;        // 第2個有序區的索引
    int k = 0;                // 臨時區域的索引

    while(i <= mid && j <= end)
    {
        if (a[i] <= a[j])
            tmp[k++] = a[i++];
        else
            tmp[k++] = a[j++];
    }

    while(i <= mid)
        tmp[k++] = a[i++];

    while(j <= end)
        tmp[k++] = a[j++];

    // 將排序後的元素,所有都整合到數組a中。
    for (i = 0; i < k; i++)
        a[start + i] = tmp[i];

    free(tmp);
}

/*
 * 歸併排序(從上往下)
 *
 * 參數說明:
 *     a -- 待排序的數組
 *     start -- 數組的起始地址
 *     endi -- 數組的結束地址
 */
void merge_sort_up2down(int a[], int start, int end)
{
    if(a==NULL || start >= end)
        return ;

    int mid = (end + start)/2;
    merge_sort_up2down(a, start, mid); // 遞歸排序a[start...mid]
    merge_sort_up2down(a, mid+1, end); // 遞歸排序a[mid+1...end]

    // a[start...mid] 和 a[mid...end]是兩個有序空間,
    // 將它們排序成一個有序空間a[start...end]
    merge(a, start, mid, end);
}


從上往下的歸併排序採用了遞歸的方式實現。它的原理很是簡單,以下圖:3d

經過"從上往下的歸併排序"來對數組{80,30,60,40,20,10,50,70}進行排序時:
1. 將數組{80,30,60,40,20,10,50,70}看做由兩個有序的子數組{80,30,60,40}和{20,10,50,70}組成。對兩個有序子樹組進行排序便可。
2. 將子數組{80,30,60,40}看做由兩個有序的子數組{80,30}和{60,40}組成。
    將子數組{20,10,50,70}看做由兩個有序的子數組{20,10}和{50,70}組成。
3. 將子數組{80,30}看做由兩個有序的子數組{80}和{30}組成。
    將子數組{60,40}看做由兩個有序的子數組{60}和{40}組成。
    將子數組{20,10}看做由兩個有序的子數組{20}和{10}組成。
    將子數組{50,70}看做由兩個有序的子數組{50}和{70}組成。code

 

歸併排序(從下往上)代碼

/*
 * 對數組a作若干次合併:數組a的總長度爲len,將它分爲若干個長度爲gap的子數組;
 *             將"每2個相鄰的子數組" 進行合併排序。
 *
 * 參數說明:
 *     a -- 待排序的數組
 *     len -- 數組的長度
 *     gap -- 子數組的長度
 */
void merge_groups(int a[], int len, int gap)
{
    int i;
    int twolen = 2 * gap;    // 兩個相鄰的子數組的長度

    // 將"每2個相鄰的子數組" 進行合併排序。
    for(i = 0; i+2*gap-1 < len; i+=(2*gap))
    {
        merge(a, i, i+gap-1, i+2*gap-1);
    }

    // 若 i+gap-1 < len-1,則剩餘一個子數組沒有配對。
    // 將該子數組合併到已排序的數組中。
    if ( i+gap-1 < len-1)
    {
        merge(a, i, i + gap - 1, len - 1);
    }
}

/*
 * 歸併排序(從下往上)
 *
 * 參數說明:
 *     a -- 待排序的數組
 *     len -- 數組的長度
 */
void merge_sort_down2up(int a[], int len)
{
    int n;

    if (a==NULL || len<=0)
        return ;

    for(n = 1; n < len; n*=2)
        merge_groups(a, len, n);
}

從下往上的歸併排序的思想正好與"從下往上的歸併排序"相反。以下圖:

經過"從下往上的歸併排序"來對數組{80,30,60,40,20,10,50,70}進行排序時:
1. 將數組{80,30,60,40,20,10,50,70}看做由8個有序的子數組{80},{30},{60},{40},{20},{10},{50}和{70}組成。
2. 將這8個有序的子數列兩兩合併。獲得4個有序的子樹列{30,80},{40,60},{10,20}和{50,70}。
3. 將這4個有序的子數列兩兩合併。獲得2個有序的子樹列{30,40,60,80}和{10,20,50,70}。
4. 將這2個有序的子數列兩兩合併。獲得1個有序的子樹列{10,20,30,40,50,60,70,80}。

 

歸併排序的時間複雜度和穩定性

歸併排序時間複雜度
歸併排序的時間複雜度是O(N*lgN)。
假設被排序的數列中有N個數。遍歷一趟的時間複雜度是O(N),須要遍歷多少次呢?
歸併排序的形式就是一棵二叉樹,它須要遍歷的次數就是二叉樹的深度,而根據徹底二叉樹的能夠得出它的時間複雜度是O(N*lgN)。

歸併排序穩定性
歸併排序是穩定的算法,它知足穩定算法的定義。
算法穩定性 -- 假設在數列中存在a[i]=a[j],若在排序以前,a[i]在a[j]前面;而且排序以後,a[i]仍然在a[j]前面。則這個排序算法是穩定的!

 

歸併排序實現

下面給出歸併排序的三種實現:C、C++和Java。這三種實現的原理和輸出結果都是同樣的,每一種實現中都包括了"從上往下的歸併排序"和"從下往上的歸併排序"這2種形式。
歸併排序C實現
實現代碼(merge_sort.c)

  1 /**
  2  * 歸併排序:C 語言
  3  *
  4  * @author skywang
  5  * @date 2014/03/12
  6  */
  7 
  8 #include <stdio.h>
  9 #include <stdlib.h>
 10 
 11 // 數組長度
 12 #define LENGTH(array) ( (sizeof(array)) / (sizeof(array[0])) )
 13 
 14 /*
 15  * 將一個數組中的兩個相鄰有序區間合併成一個
 16  *
 17  * 參數說明:
 18  *     a -- 包含兩個有序區間的數組
 19  *     start -- 第1個有序區間的起始地址。
 20  *     mid   -- 第1個有序區間的結束地址。也是第2個有序區間的起始地址。
 21  *     end   -- 第2個有序區間的結束地址。
 22  */
 23 void merge(int a[], int start, int mid, int end)
 24 {
 25     int *tmp = (int *)malloc((end-start+1)*sizeof(int));    // tmp是彙總2個有序區的臨時區域
 26     int i = start;            // 第1個有序區的索引
 27     int j = mid + 1;        // 第2個有序區的索引
 28     int k = 0;                // 臨時區域的索引
 29 
 30     while(i <= mid && j <= end)
 31     {
 32         if (a[i] <= a[j])
 33             tmp[k++] = a[i++];
 34         else
 35             tmp[k++] = a[j++];
 36     }
 37 
 38     while(i <= mid)
 39         tmp[k++] = a[i++];
 40 
 41     while(j <= end)
 42         tmp[k++] = a[j++];
 43 
 44     // 將排序後的元素,所有都整合到數組a中。
 45     for (i = 0; i < k; i++)
 46         a[start + i] = tmp[i];
 47 
 48     free(tmp);
 49 }
 50 
 51 /*
 52  * 歸併排序(從上往下)
 53  *
 54  * 參數說明:
 55  *     a -- 待排序的數組
 56  *     start -- 數組的起始地址
 57  *     endi -- 數組的結束地址
 58  */
 59 void merge_sort_up2down(int a[], int start, int end)
 60 {
 61     if(a==NULL || start >= end)
 62         return ;
 63 
 64     int mid = (end + start)/2;
 65     merge_sort_up2down(a, start, mid); // 遞歸排序a[start...mid]
 66     merge_sort_up2down(a, mid+1, end); // 遞歸排序a[mid+1...end]
 67 
 68     // a[start...mid] 和 a[mid...end]是兩個有序空間,
 69     // 將它們排序成一個有序空間a[start...end]
 70     merge(a, start, mid, end);
 71 }
 72 
 73 
 74 /*
 75  * 對數組a作若干次合併:數組a的總長度爲len,將它分爲若干個長度爲gap的子數組;
 76  *             將"每2個相鄰的子數組" 進行合併排序。
 77  *
 78  * 參數說明:
 79  *     a -- 待排序的數組
 80  *     len -- 數組的長度
 81  *     gap -- 子數組的長度
 82  */
 83 void merge_groups(int a[], int len, int gap)
 84 {
 85     int i;
 86     int twolen = 2 * gap;    // 兩個相鄰的子數組的長度
 87 
 88     // 將"每2個相鄰的子數組" 進行合併排序。
 89     for(i = 0; i+2*gap-1 < len; i+=(2*gap))
 90     {
 91         merge(a, i, i+gap-1, i+2*gap-1);
 92     }
 93 
 94     // 若 i+gap-1 < len-1,則剩餘一個子數組沒有配對。
 95     // 將該子數組合併到已排序的數組中。
 96     if ( i+gap-1 < len-1)
 97     {
 98         merge(a, i, i + gap - 1, len - 1);
 99     }
100 }
101 
102 /*
103  * 歸併排序(從下往上)
104  *
105  * 參數說明:
106  *     a -- 待排序的數組
107  *     len -- 數組的長度
108  */
109 void merge_sort_down2up(int a[], int len)
110 {
111     int n;
112 
113     if (a==NULL || len<=0)
114         return ;
115 
116     for(n = 1; n < len; n*=2)
117         merge_groups(a, len, n);
118 }
119 
120 void main()
121 {
122     int i;
123     int a[] = {80,30,60,40,20,10,50,70};
124     int ilen = LENGTH(a);
125 
126     printf("before sort:");
127     for (i=0; i<ilen; i++)
128         printf("%d ", a[i]);
129     printf("\n");
130 
131     merge_sort_up2down(a, 0, ilen-1);        // 歸併排序(從上往下)
132     //merge_sort_down2up(a, ilen);            // 歸併排序(從下往上)
133 
134     printf("after  sort:");
135     for (i=0; i<ilen; i++)
136         printf("%d ", a[i]);
137     printf("\n");
138 }
View Code

歸併排序C++實現
實現代碼(MergeSort.cpp)

  1 /**
  2  * 歸併排序:C++
  3  *
  4  * @author skywang
  5  * @date 2014/03/12
  6  */
  7 
  8 #include <iostream>
  9 using namespace std;
 10 
 11 /*
 12  * 將一個數組中的兩個相鄰有序區間合併成一個
 13  *
 14  * 參數說明:
 15  *     a -- 包含兩個有序區間的數組
 16  *     start -- 第1個有序區間的起始地址。
 17  *     mid   -- 第1個有序區間的結束地址。也是第2個有序區間的起始地址。
 18  *     end   -- 第2個有序區間的結束地址。
 19  */
 20 void merge(int* a, int start, int mid, int end)
 21 {
 22     int *tmp = new int[end-start+1];    // tmp是彙總2個有序區的臨時區域
 23     int i = start;            // 第1個有序區的索引
 24     int j = mid + 1;        // 第2個有序區的索引
 25     int k = 0;                // 臨時區域的索引
 26 
 27     while(i <= mid && j <= end)
 28     {
 29         if (a[i] <= a[j])
 30             tmp[k++] = a[i++];
 31         else
 32             tmp[k++] = a[j++];
 33     }
 34 
 35     while(i <= mid)
 36         tmp[k++] = a[i++];
 37 
 38     while(j <= end)
 39         tmp[k++] = a[j++];
 40 
 41     // 將排序後的元素,所有都整合到數組a中。
 42     for (i = 0; i < k; i++)
 43         a[start + i] = tmp[i];
 44 
 45     delete[] tmp;
 46 }
 47 
 48 /*
 49  * 歸併排序(從上往下)
 50  *
 51  * 參數說明:
 52  *     a -- 待排序的數組
 53  *     start -- 數組的起始地址
 54  *     endi -- 數組的結束地址
 55  */
 56 void mergeSortUp2Down(int* a, int start, int end)
 57 {
 58     if(a==NULL || start >= end)
 59         return ;
 60 
 61     int mid = (end + start)/2;
 62     mergeSortUp2Down(a, start, mid); // 遞歸排序a[start...mid]
 63     mergeSortUp2Down(a, mid+1, end); // 遞歸排序a[mid+1...end]
 64 
 65     // a[start...mid] 和 a[mid...end]是兩個有序空間,
 66     // 將它們排序成一個有序空間a[start...end]
 67     merge(a, start, mid, end);
 68 }
 69 
 70 
 71 /*
 72  * 對數組a作若干次合併:數組a的總長度爲len,將它分爲若干個長度爲gap的子數組;
 73  *             將"每2個相鄰的子數組" 進行合併排序。
 74  *
 75  * 參數說明:
 76  *     a -- 待排序的數組
 77  *     len -- 數組的長度
 78  *     gap -- 子數組的長度
 79  */
 80 void mergeGroups(int* a, int len, int gap)
 81 {
 82     int i;
 83     int twolen = 2 * gap;    // 兩個相鄰的子數組的長度
 84 
 85     // 將"每2個相鄰的子數組" 進行合併排序。
 86     for(i = 0; i+2*gap-1 < len; i+=(2*gap))
 87     {
 88         merge(a, i, i+gap-1, i+2*gap-1);
 89     }
 90 
 91     // 若 i+gap-1 < len-1,則剩餘一個子數組沒有配對。
 92     // 將該子數組合併到已排序的數組中。
 93     if ( i+gap-1 < len-1)
 94     {
 95         merge(a, i, i + gap - 1, len - 1);
 96     }
 97 }
 98 
 99 /*
100  * 歸併排序(從下往上)
101  *
102  * 參數說明:
103  *     a -- 待排序的數組
104  *     len -- 數組的長度
105  */
106 void mergeSortDown2Up(int* a, int len)
107 {
108     int n;
109 
110     if (a==NULL || len<=0)
111         return ;
112 
113     for(n = 1; n < len; n*=2)
114         mergeGroups(a, len, n);
115 }
116 
117 int main()
118 {
119     int i;
120     int a[] = {80,30,60,40,20,10,50,70};
121     int ilen = (sizeof(a)) / (sizeof(a[0]));
122 
123     cout << "before sort:";
124     for (i=0; i<ilen; i++)
125         cout << a[i] << " ";
126     cout << endl;
127 
128     mergeSortUp2Down(a, 0, ilen-1);        // 歸併排序(從上往下)
129     //mergeSortDown2Up(a, ilen);            // 歸併排序(從下往上)
130 
131     cout << "after  sort:";
132     for (i=0; i<ilen; i++)
133         cout << a[i] << " ";
134     cout << endl;
135 
136     return 0;
137 }
View Code

歸併排序Java實現
實現代碼(MergeSort.java)

  1 /**
  2  * 歸併排序:Java
  3  *
  4  * @author skywang
  5  * @date 2014/03/12
  6  */
  7 
  8 public class MergeSort {
  9 
 10     /*
 11      * 將一個數組中的兩個相鄰有序區間合併成一個
 12      *
 13      * 參數說明:
 14      *     a -- 包含兩個有序區間的數組
 15      *     start -- 第1個有序區間的起始地址。
 16      *     mid   -- 第1個有序區間的結束地址。也是第2個有序區間的起始地址。
 17      *     end   -- 第2個有序區間的結束地址。
 18      */
 19     public static void merge(int[] a, int start, int mid, int end) {
 20         int[] tmp = new int[end-start+1];    // tmp是彙總2個有序區的臨時區域
 21         int i = start;            // 第1個有序區的索引
 22         int j = mid + 1;        // 第2個有序區的索引
 23         int k = 0;                // 臨時區域的索引
 24 
 25         while(i <= mid && j <= end) {
 26             if (a[i] <= a[j])
 27                 tmp[k++] = a[i++];
 28             else
 29                 tmp[k++] = a[j++];
 30         }
 31 
 32         while(i <= mid)
 33             tmp[k++] = a[i++];
 34 
 35         while(j <= end)
 36             tmp[k++] = a[j++];
 37 
 38         // 將排序後的元素,所有都整合到數組a中。
 39         for (i = 0; i < k; i++)
 40             a[start + i] = tmp[i];
 41 
 42         tmp=null;
 43     }
 44 
 45     /*
 46      * 歸併排序(從上往下)
 47      *
 48      * 參數說明:
 49      *     a -- 待排序的數組
 50      *     start -- 數組的起始地址
 51      *     endi -- 數組的結束地址
 52      */
 53     public static void mergeSortUp2Down(int[] a, int start, int end) {
 54         if(a==null || start >= end)
 55             return ;
 56 
 57         int mid = (end + start)/2;
 58         mergeSortUp2Down(a, start, mid); // 遞歸排序a[start...mid]
 59         mergeSortUp2Down(a, mid+1, end); // 遞歸排序a[mid+1...end]
 60 
 61         // a[start...mid] 和 a[mid...end]是兩個有序空間,
 62         // 將它們排序成一個有序空間a[start...end]
 63         merge(a, start, mid, end);
 64     }
 65 
 66 
 67     /*
 68      * 對數組a作若干次合併:數組a的總長度爲len,將它分爲若干個長度爲gap的子數組;
 69      *             將"每2個相鄰的子數組" 進行合併排序。
 70      *
 71      * 參數說明:
 72      *     a -- 待排序的數組
 73      *     len -- 數組的長度
 74      *     gap -- 子數組的長度
 75      */
 76     public static void mergeGroups(int[] a, int len, int gap) {
 77         int i;
 78         int twolen = 2 * gap;    // 兩個相鄰的子數組的長度
 79 
 80         // 將"每2個相鄰的子數組" 進行合併排序。
 81         for(i = 0; i+2*gap-1 < len; i+=(2*gap))
 82             merge(a, i, i+gap-1, i+2*gap-1);
 83 
 84         // 若 i+gap-1 < len-1,則剩餘一個子數組沒有配對。
 85         // 將該子數組合併到已排序的數組中。
 86         if ( i+gap-1 < len-1)
 87             merge(a, i, i + gap - 1, len - 1);
 88     }
 89 
 90     /*
 91      * 歸併排序(從下往上)
 92      *
 93      * 參數說明:
 94      *     a -- 待排序的數組
 95      */
 96     public static void mergeSortDown2Up(int[] a) {
 97         if (a==null)
 98             return ;
 99 
100         for(int n = 1; n < a.length; n*=2)
101             mergeGroups(a, a.length, n);
102     }
103 
104     public static void main(String[] args) {
105         int i;
106         int a[] = {80,30,60,40,20,10,50,70};
107 
108         System.out.printf("before sort:");
109         for (i=0; i<a.length; i++)
110             System.out.printf("%d ", a[i]);
111         System.out.printf("\n");
112 
113         mergeSortUp2Down(a, 0, a.length-1);        // 歸併排序(從上往下)
114         //mergeSortDown2Up(a);                    // 歸併排序(從下往上)
115 
116         System.out.printf("after  sort:");
117         for (i=0; i<a.length; i++)
118             System.out.printf("%d ", a[i]);
119         System.out.printf("\n");
120     }
121 }
View Code

 

上面3種實現的原理和輸出結果都是同樣的。下面是它們的輸出結果:

before sort:80 30 60 40 20 10 50 70 
after  sort:10 20 30 40 50 60 70 80 
相關文章
相關標籤/搜索