分治法與歸並排序


本文部分內容參考了《算法導論》

分治策略

  解決一個給定問題,算法需要一次或多次地遞歸調用自身來解決相關的子問題,這種算法通常采用分治策略。分治模式在每一層遞歸上都有三個步驟:

  〉〉分解:將原問題分解成一系列子問題

  〉〉解決:遞歸地求解各子問題。若子問題足夠小,則直接求解

  〉〉合並:將子問題的結果合並成原問題的解。

 

歸並排序(合並排序)

  歸並排序的關鍵在於歸並兩個相鄰的子序列,使其變成一個排序好的新序列。如果這個新序列就是原來需要進行排序的數組,那么排序完成。所以,我們需要將原序列遞歸地分成若干子序列,直道最小的子序列只有一個元素,然后將子序列依次歸並,就可以得到排序好的原序列。

歸並兩個子序列

  我們要解決的第一個問題就是:假設有子序列A[p...q]和子序列[q + 1...r]是已經排序好的子序列,如何按順序將它們歸並到一個子序列中去呢

  我們可以用撲克牌做模擬。將兩堆數量相近的撲克牌按順序疊好,最小的牌放在最上面,牌面朝上。

  〉〉第一步:拿出兩張中較小的一張,放在桌上。

  〉〉第二步:分別比較所拿的牌和兩個堆上面的牌,重復第一步,跟在前一張牌的后面。直到一個堆拿完。

  〉〉第三步:將剩余的堆從上往下一次放在桌上,跟在前一張牌的后面。

  由此可見,按問題要求(加橙色的),我們可以設計如下代碼:

void merge(int ar[], int p, int q, int r, int temp[]){
        //將ar[p...q]和ar[q+1...r]合並到temp[p...r],temp由外部分配內存
    int i = p, j = q + 1, k = 0;
    while(i <= q && j <= r){
        if(ar[i] < ar[j])
            temp[k++] = ar[i++];
        else
            temp[k++] = ar[j++];
    }
    while(i <= q) //如果ar[p..q]有剩
        temp[k++] = ar[i++];
    while(j <= r) //如果ar[q+1..r]有剩
        temp[k++] = ar[j++];

    for(k = 0; k <= (r - p); k++) //將合並后的子序列賦值給原序列ar[p...r]
        ar[p + k] = temp[k];
}

 

利用分治策略使原序列有序

  對於歸並排序,我們要解決的第二個問題就是:原序列如何成為有序序列

  我們可以通過將原序列二分為兩個子序列,再將兩個子序列二分為另外的四個子序列,直到不能再分解為止。然后分別合並相鄰的兩個子序列,直到不能合並為止。這里引用網絡上的一張圖修改后來說明這個原理。

 

   前面提到過,解決一個給定問題,算法需要一次或多次地遞歸調用自身來解決相關的子問題,這種算法通常采用分治策略。所以,我們可以利用分治策略通過通過遞歸調用一個函數來解決。我們可以這樣寫代碼:

void mergesort(int ar[], int head, int end, int temp[]){
    if(head < end){ //條件:直到不能分割為止
        int middle = (head + end) / 2; //找到二分原序列的點
        mergesort(ar, head, middle, temp); //左子序列排序
        mergesort(ar, middle + 1, end, temp); //右子序列排序
        merge(ar, head, middle, end, temp); //合並這兩個子序列
    }
}

   分析上面的代碼,我們可以將上圖標上執行順序,來驗證該算法的正確性。同時,你也可以通過類似“循環不變式”的方法證明:

完整代碼

  到這里,歸並排序的所有問題都解決完了。我們給出完整的不帶注釋(分配內存后的空注釋是標志,提醒一定要及時釋放內存)的代碼。

  *說明一下,MergeSort中的mergesort()函數和更好的MergeSort中的_mergesort()函數中並沒有創建middle變量來存儲二分原序列的點,原因是在遞歸調用函數時我們應盡量避免創建不太需要的變量導致內存占用的飆升(浪費)。

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <conio.h>
 4 #include <time.h>
 5 
 6 void mergesort(int [], int, int, int []);
 7 void merge(int [], int, int, int, int[]);
 8 
 9 int main(int argc, char * argv[]){
10     int * ar, * temp;
11     int n = 10, i;
12 
13     if( !(ar = (int *)malloc( (size_t)n * sizeof(int) ) ) )//
14         exit(EXIT_FAILURE);
15     if( !(temp = (int *)malloc( (size_t)n * sizeof(int) ) ) )//
16         exit(EXIT_FAILURE);
17 
18     srand( (unsigned int)time(NULL) );
19     for(i = 0; i < n; i++){
20         ar[i] = rand() % 1000;
21         printf("%-4d",ar[i]);
22     }
23 
24     mergesort(ar, 0, n - 1, temp);
25 
26     printf("\n歸並排序后:\n");
27     for(i = 0; i < n; i++)
28         printf("%-4d",ar[i]);
29 
30     free(temp);
31     free(ar);
32 
33     _getch();
34     return 0;
35 }
36 
37 void mergesort(int ar[], int head, int end, int temp[]){
38     if(head < end){
39         int middle = (head + end) / 2;
40         mergesort(ar, head, middle, temp);
41         mergesort(ar, middle + 1, end, temp);
42         merge(ar, head, middle, end, temp);
43     }
44 }
45 
46 void merge(int ar[], int p, int q, int r, int temp[]){
47     int i = p, j = q + 1, k = 0;
48     while(i <= q && j <= r){
49         if(ar[i] < ar[j])
50             temp[k++] = ar[i++];
51         else
52             temp[k++] = ar[j++];
53     }
54     while(i <= q)
55         temp[k++] = ar[i++];
56     while(j <= r)
57         temp[k++] = ar[j++];
58 
59     for(k = 0; k <= (r - p); k++)
60         ar[p + k] = temp[k];
61 }
MergeSort

   下面這個是帶有入口函數的,只要在main()函數中把數組名和大小傳遞給mergesort()函數就行了。

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <conio.h>
 4 #include <time.h>
 5 
 6 void mergesort(float *, int);
 7 void _mergesort(float *, int, int, float *);
 8 void merge(float *, int, int, int, float *);
 9 
10 int main(int argc, char * argv[]){
11     float * ar;
12     int n = 10, i;
13 
14     if( !(ar = (float *)malloc( (size_t)n * sizeof(float) ) ) )//
15         exit(EXIT_FAILURE);
16 
17     srand( (unsigned int)time(NULL) );
18     for(i = 0; i < n; i++){
19         ar[i] = (float)((rand() % 499)) / (float)((rand() % 11 + 1));
20         printf("%-6.3g",ar[i]);
21     }
22 
23     mergesort(ar, n);
24 
25     printf("\n歸並排序后:\n");
26     for(i = 0; i < n; i++)
27         printf("%-6.3g",ar[i]);
28 
29     free(ar);
30 
31     _getch();
32     return 0;
33 }
34 
35 void mergesort(float * ar, int size){
36     if(size > 0){
37         float * temp;
38         if(!(temp = (float *)malloc( (size_t)size * sizeof(float) ) ) ) //
39             exit(EXIT_FAILURE);
40         _mergesort(ar, 0, size - 1, temp);
41         free(temp);
42     }
43 }
44 
45 void _mergesort(float * ar, int head, int end, float * temp){
46     if(head < end){
47         _mergesort(ar, head, (head + end) / 2, temp);
48         _mergesort(ar, (head + end) / 2 + 1, end, temp);
49         merge(ar, head, (head + end) / 2, end, temp);
50     }
51 }
52 
53 void merge(float * ar, int p, int q, int r, float * temp){
54     int i = p, j = q + 1, k = 0;
55     while(i <= q && j <= r){
56         if( ar[i] < ar[j] )
57             temp[k++] = ar[i++];
58         else
59             temp[k++] = ar[j++];
60     }
61     while(i <= q)
62         temp[k++] = ar[i++];
63     while(j <= r)
64         temp[k++] = ar[j++];
65 
66     for(k = 0; k <= (r - p); k++)
67         ar[p + k] = temp[k];
68 }
更好的MergeSort


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM