程序员面试系列——合并排序(递归实现)
合并排序基本思想
合并排序,或者叫歸并排序,在算法思想中屬于分治法。對于一個需要排序的數(shù)組,合并排序把它一分為二,并對每個子數(shù)組遞歸排序,然后把這兩個排好序的子數(shù)組合并為一個有序數(shù)組。
本文要介紹兩種實現(xiàn),都是基于遞歸(自頂向下)。這兩種實現(xiàn)沒有本質(zhì)的差別,第二種只是為了節(jié)省內(nèi)存,提高效率。
第一種實現(xiàn)
假設(shè)A數(shù)組是要排序的數(shù)組。
1. 把A數(shù)組的前半部分復(fù)制到B數(shù)組,把A數(shù)組的后半部分復(fù)制到C數(shù)組
2. 對B數(shù)組和C數(shù)組分別排序(遞歸);之后,B數(shù)組和C數(shù)組已經(jīng)有序
3. 把數(shù)組B和C合并,成為有序數(shù)組A(見函數(shù)__merge)
C語言代碼如下。
/** 將兩個有序數(shù)組b和c合并為一個有序數(shù)組a* b是第一個非降序數(shù)組的首地址,長度是len_b* c是第二個非降序數(shù)組的首地址,長度是len_c* a指向輸出數(shù)組的首地址* 注意:a數(shù)組需要調(diào)用者分配空間,且a數(shù)組不能與b或者c重疊*/void __merge(int *a, int *b, int len_b, int *c, int len_c) {int i = 0; // b的下標int j = 0; // c的下標int k = 0; // a的下標int len = len_b + len_c; //計算出a數(shù)組的長度while(i < len_b && j < len_c) //&&的優(yōu)先級更低 {if(b[i] <= c[j])a[k++] = b[i++];elsea[k++] = c[j++];}if(i == len_b) //說明b已經(jīng)處理完memcpy(a+k, c+j, (len-k)*sizeof(int));else //說明c已經(jīng)處理完memcpy(a+k, b+i, (len-k)*sizeof(int)); }上面的這個函數(shù)完成“合二為一”的工作,即把非降序的B和C合并為非降序的A.
/** a是數(shù)組首地址,數(shù)組長度為len* 將數(shù)組a按照非降序排列*/ void merge_sort(int *a, int len) {if(len==1) //遞歸出口return;int len_b = len/2;int len_c = len - len_b;int *temp = (int *)malloc(sizeof(int)*len);//為數(shù)組b和c分配空間if(temp == NULL){printf("malloc failed\n");return;}memcpy(temp, a, len*sizeof(int));int *b = temp;int *c = temp+len_b; //以上三行完成了復(fù)制和拆分merge_sort(b,len_b); //遞歸調(diào)用,對數(shù)組b排序merge_sort(c,len_c); //遞歸調(diào)用,對數(shù)組c排序__merge(a, b, len_b, c, len_c); //合并b和cfree(temp);}merge_sort這個函數(shù)才是歸并排序的主函數(shù)。
為了更好地理解,我畫了一幅示意圖。此圖體現(xiàn)出遞歸出口(即遞歸結(jié)束條件)是數(shù)組長度等于1.
merge_sort函數(shù)在設(shè)計上的不足之處是內(nèi)存開銷大,也費時間(malloc函數(shù)用多了會影響性能)。
因為要為數(shù)組B和數(shù)組C分配空間,然后分別排序(因為是遞歸,所以又要分配與釋放),排序完畢后再把二者合并到A數(shù)組,此時數(shù)組B和數(shù)組C的空間才可以釋放。
可否改進一下呢?可以看看第二種實現(xiàn)。
第二種實現(xiàn)
假設(shè)A數(shù)組是要排序的數(shù)組。
1. 先分配一段內(nèi)存(這里叫temp),其大小和數(shù)組A一樣大
2. 把A數(shù)組一分為二,前半部分稱為B數(shù)組,后半部分稱為C數(shù)組
3. 對B數(shù)組和C數(shù)組分別排序(遞歸);排序時,借助temp(借助的原因請看4和5)
4. 把數(shù)組B和C合并為有序數(shù)組,合并的結(jié)果在temp中。(利用函數(shù)__merge)
5. 把temp的內(nèi)容拷貝到數(shù)組A
6. 釋放temp
這樣做的特點是一次分配,反復(fù)利用,最后釋放。優(yōu)點是省內(nèi)存,效率高。
C語言代碼如下。
/**內(nèi)部函數(shù)*temp是一段內(nèi)存的首地址,由調(diào)用者分配和釋放*/ void __merge_sort(int *a, int len, int *temp) {if(len==1) //遞歸出口return;int len_b = len/2;int len_c = len - len_b;int *b = a;int *c = a+len_b; //把數(shù)組a原地拆分為b和c__merge_sort(b,len_b,temp); //對數(shù)組b排序,借助內(nèi)存temp__merge_sort(c,len_c,temp); //對數(shù)組c排序,借助內(nèi)存temp__merge(temp, b, len_b, c, len_c); //合并b和c到tempmemcpy(a,temp,len*sizeof(int)); //復(fù)制temp到數(shù)組a }/**這種寫法只需要申請一次空間,節(jié)省內(nèi)存,效率更高*/ void merge_sort(int *a, int len) {int *temp = (int *)malloc(sizeof(int)*len);if(temp == NULL){printf("malloc failed\n");return;}__merge_sort(a,len,temp); //此函數(shù)實現(xiàn)見上文free(temp); }測試
注意,以上代碼需要包含的頭文件是
#include <string.h> //memcpy函數(shù)用這個頭文件 #include <stdlib.h> //malloc和free函數(shù)用這個頭文件測試代碼如下。
#include <stdio.h>/**此函數(shù)為了測試用,功能是打印數(shù)組*a是數(shù)組首地址*len是數(shù)組長度*/ void print_array(int *a, int len) {int i=0;for(i=0; i<len; ++i)printf("%d ",a[i]);printf("\n"); }//測試函數(shù) int main(void) {int a[10]={9,8,5,3,2,9,-1,-1,7,1,}; //10個數(shù)merge_sort(a,10); //按照非降序排列print_array(a,10); //打印排序后的數(shù)組return 0; }運行結(jié)果是:
-1 -1 1 2 3 5 7 8 9 9
—–歡迎斧正—–
【參考資料】
《算法設(shè)計與分析基礎(chǔ)(第3版)》(清華大學出版社)
總結(jié)
以上是生活随笔為你收集整理的程序员面试系列——合并排序(递归实现)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 访问堆中的数据成员
- 下一篇: 用冒泡法对10个整数从小到大排序