查看: 5093| 回复: 3
跳转到指定楼层
上一主题 下一主题
收起左侧

[二分/排序/搜索] MergeSort 講解 ( Recursive and Iterative)

全局:

注册一亩三分地论坛,查看更多干货!

您需要 登录 才可以下载或查看附件。没有帐号?注册账号

x
本帖最后由 brilight 于 2013-2-17 05:50 编辑

論壇上 已經有同學寫了 Bubble, insertion, selection, quick, shell 五種排序算法了,如下鏈接:
http://www.1point3acres.com/bbs/forum.php?mod=forumdisplay&fid=84&filter=typeid&typeid=199

我看了之後覺得很有用,所以自己再寫個 MergeSort 吧,作為以上5種排序的補充。

如果有疑問,或者有更快速更簡便的方法,希望大家提意見。

MergeSort is a very typical algorithm which uses Divide and Conquer.  The hard problem in this algorithm is that,for two already sorted arrays, how can you merge them into a new sorted arrayin linear time? Assume that the a[0…3] is a sorted array, and a[4…8] is another sorted array.

  index  01234=middle5678
  value  111314181012151718

We want to merge them so that a[0…8] will be  
  index  01234=middle5678
  value  101112131415171818


現在開始merge,
算法就是比較 a[0…3] 中最小值和 a[4….8]中最小值,最開始 i=0, j=4,比較11 和10,
  index  i=0123j=45678
  value  111314181012151718

10<11,把10移動到 臨時的空間 temp[0...8]裡面。然後 j向右移動,j=5, 再比11 和 12,
  index  i=01234j=5678
  value  11131418

12151718

11<12, 把11移到temp[0...8]裡面,i向右移動, i=1
  index  0i=1234j=5678
  value  

131418

12151718
再比13和 12,把12移走,再比13 和 15,把13移走… 最後 temp[0...8]就是排好序的了

So we have a function

void merge(int a[], int middle, int n)
/*
Where “a” is a sorted array whose length is “middle”, and “ a+middle” is another sorted array whose length is “n-middle”. Our goal is to sort them into a single array “a”,上表格中 middle =4 , n=9
*/
{

   // k from 0 to n
   // i from 0 to middle , j from middle to end
   int i, j, k;
   if(n <= middle) return;

   int *temp = (int*) malloc(sizeof(int) * n);

   for( i = 0, j = middle,k=0 ; i < middle && j < n ; k++) {
      if(a[ i ] < a[j])
         temp[k] = a[i++];
      else
         temp[k] = a[j++];
   }

   while(i < middle)  temp[k++] = a[i++];
   while(j < n)       temp[k++] = a[j++];

   for(k = 0; k < n; k++) {
      a[k] = temp[k];
   }
   free(temp);
   /* 這種寫法 需要不停地 malloc, 會影響程序的速度,我們可以給此函數加一個參數,把臨時空間放在此函數的外面,既在此函數外面 調用malloc ,這樣就只用調用malloc 一次了, 具體的代碼實現 會影響程序的可讀性,所以我在這裡就不寫了。*/

}


由於需要臨時空間temp[0…n],MergeSort要用到 O(n)空間,我曾經想過, In-place merge sort有沒有可能?
我曾經寫的In-place merge, 代碼如下:

void mergeWithoutExtraSpace(int a[], int middle, int n)
{
   // a = array
   // num1 = number of elements in the first half array
   // num2 = ......................... second .........

   int *k;
   int temp;
   int *b;
   int num1= middle;
   int num2 = n - num1;

   b = a + num1; // b points to the second half array

   while(num1 > 0 && num2 > 0) {
      if(*b < *a) {
         temp = *b;
         for(k = b; k > a; k--) {
            *k = * (k - 1);   // use insertion which is O(n^2)
         }
         *a = temp;
         b++;
         num2--;
      } else {
         num1--;
      }
      a++;
   }
}

上面的 mergeWithoutExtraSpace 函數 用了 插入法 。雖然 比較的次數仍然是O(n), 但 插入需要O(n^2)。  
所以 This version above saves space but uses O(n^2) time ! Do not use it for merge sort!

下面就開始正式 mergesort了。
原理是 把一個array從中間分成左右兩份,左邊排序,右邊排序,然後 merge,就成功了。
Recursion 和 iteration 都可以實現 mergesort , 下面分開介紹:

Recursive寫法
void mergeSortRecursive(int a[], int n)  
//a 是 array, n是array的長度,halflength 是 平均分割後左邊的長度。
{
   int halfLength;

   if(n <= 1) return;
   halfLength = n / 2;

   mergeSortRecursive(a, halfLength); //左邊排序
   mergeSortRecursive(&a[halfLength], n - halfLength); //右邊排序
   merge(a, halfLength, n);  // 合成兩個已經排好的
}
Recursive 不難,就不用多說了。

Iterative 寫法:
void mergeSort(int a[], int n)
{
#define min(a,b) ((a)<(b)?(a):(b))

   int i, j;

   // 2*i is the length of merged result

   for(i = 1; i < n; i *= 2) {
      for(j = 0;  j < n ; j += 2 * i) {         
         merge(a + j, min(i,n-j) , min(2 * i,n-j) ); // 用 min(i,n-j) , 是因為最後一個 element 不能超出 array 邊界。
      }
   }
}


  index  012345678
  i=1  j

j

j

j

j
  i=2  j





j





j
  i=4  j













j
  i=8  j

















當i=1時,j 的位置是 0,2,4,6,8,array 中的元素 一個一個組合,合成後的長度為2 。但最後一個 a[8] 無法配對。
當i=2時,j 的位置是 0,   4,   8,  array 中的元素 兩個兩個組合,合成後的長度為4。 但最後一個 a[8] 無法配對。

但以上的代碼有2個缺陷:
1. 每次 j 循環 都要判斷 merge的 是不是最後一個 元素 a[8],如果是 則取min(i,n-j)
2. 每次 i 循環 都調用 merge (&a[8],1,1), 而a[8] 已經被merge好了,並不需要額外的調用。

我們改進一下:
由於最後一個array的長度不定, 在 j 循環的最後一次,( 遇到 a[8] 時),我們應該直接跳出循環,  單獨處理 最後一個 element。修改後最終如下:  

void mergeSort(int a[], int n)
{
   int i, j;
   int halflength,length;

   // 2*i is the length of merged result
   for(i = 1; i < n; i *= 2) {
      for(j = 0;  j < n - 2 * i ; j += 2 * i) {
         // the last element has variable size. Leave it unmerged
         merge(a + j, i , 2 * i);
      }

      // The range of j is between [n-2i,n)
      // Merge the last element in the array

      if(i<n-j) {
         merge(a + j, i ,n-j);
      }
   }
}

评分

参与人数 1大米 +60 收起 理由
北美农民 + 60 鼓励一下,不过 LZ要考虑教学难度。。

查看全部评分


上一篇:请问Careercup要用哪种语言写啊?
下一篇:bit vector 会比boolean array 节约很多内存么?
🔗
列宾 2012-12-29 09:59:56 | 只看该作者
全局:
先顶再看。。。看起来牛爆了的样子。。
回复

使用道具 举报

🔗
风一样的人 2012-12-29 10:01:54 | 只看该作者
全局:
列宾 发表于 2012-12-29 09:59
先顶再看。。。看起来牛爆了的样子。。

我是来赞你头像的
回复

使用道具 举报

🔗
北美农民 2013-1-12 17:08:30 | 只看该作者
全局:
过多的malloc和free对运行时间不利。 可以之前就启用临时数组temp, 最后销了。

建议Iterative 写法需要更详尽的解释~

写得不错!

评分

参与人数 1大米 +5 收起 理由
brilight + 5 謝謝指點~

查看全部评分

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册账号
隐私提醒:
  • ☑ 禁止发布广告,拉群,贴个人联系方式:找人请去🔗同学同事飞友,拉群请去🔗拉群结伴,广告请去🔗跳蚤市场,和 🔗租房广告|找室友
  • ☑ 论坛内容在发帖 30 分钟内可以编辑,过后则不能删帖。为防止被骚扰甚至人肉,不要公开留微信等联系方式,如有需求请以论坛私信方式发送。
  • ☑ 干货版块可免费使用 🔗超级匿名:面经(美国面经、中国面经、数科面经、PM面经),抖包袱(美国、中国)和录取汇报、定位选校版
  • ☑ 查阅全站 🔗各种匿名方法

本版积分规则

>
快速回复 返回顶部 返回列表