【算法】_018_归并排序

1、综述

 

         “归并排序算法的关键操作是‘合并’步骤中两个已排序序列的合并。”——《算法导论》,原书第3版,机械工业出版社(殷建平等译)

 

         最近看《剑指 Offer》,第36题迟迟拿不下。回头看算法导论归并排序部分,豁然开朗。在《算法导论》中,归并排序作为分治法的典型实例,作者对其进行了详细解析。

 

分析“归并排序算法的关键操作是‘合并’步骤中两个已排序序列的合并。”这一句可以得到两层含义:

         1、“已排序序列”一词说明:基于分治法的归并排序在合并子数组之前已经递归调用自身完成了子数组的排序工作,也即合并部分是在左右两个子数组递归排序之后进行

         2、由此,归并函数可以大致分为两部分:递归左右子数组的递归部分以及合并左右数组的循环部分

 

2、循环部分

 

         假设有数组a,其定义为:

                                                                 inta[] = { 1, 3, 5, 2, 4, 6 };

 

         其中,左边三个数字和右边三个数字分别按升序排列。定义数组长度n1、n2代表左右子数组的长度,显然有:n1=3,n2=3。

         分别从左右子数组头部开始遍历,显然,左边第一个数1小于右边第一个数2,所以数组a最小值为1;在比较左边第二个数3和右边第一个数2,显然,2<3,所以数组a第二小的值为2;依此类推,直至左右子数组中有一个数组遍历完毕

         显然,此时未遍历完毕的数组中剩余的数字比另一个数组中所有的数都大,直接将其按序填入数组a即可。代码如下:

 

// 核心步骤:合并两个已排序的子数组
void CoreMerge(int* a, int n1, int n2) {
 
         //获取子区间各自的长度
         int*a1 = (int*)malloc(n1*sizeof(int));
         int*a2 = (int*)malloc(n2*sizeof(int));
 
         //拷贝子区间数据到缓冲区
         for(inti=0; i<n1; i++)
                   a1[i]= a[i];
         for(inti=0; i<n2; i++)
                   a2[i]= a[n1+i];
 
         //依次比较缓冲区数据大小填入原数组
         inti=0, j=0, k=0;
         for(;i<n1&&j<n2; k++) {
                   if(a1[i]<a2[j]){
                            a[k]= a1[i];
                            i++;
                   }else {
                            a[k]= a2[j];
                            j++;
                   }
         }
 
         //将某一个数组中未取完的数据填入剩余空间
         if(i==n1){
                   for(;j<n2; k++) {
                            a[k]= a2[j];
                            j++;
                   }
         }else {
                   for(;i<n1; k++) {
                            a[k]= a1[i];
                            i++;
                   }
         }
 
         //释放缓冲区
         free(a1);
         free(a2);
}

 

3、递归部分

         有了以上分析,递归部分可以很快写出:

 

// 递归部分
void MergeSort(int* a, int n) {
        
         //空指针或长度小于1
         if(!a|| n<2) {
                   return;
         }
 
         //递归左右子数组
         intn1 = n/2, n2 = n-n1;
         MergeSort(a,n1);
         MergeSort(a+n1,n2);
 
         //合并两个已排序子数组
         CoreMerge(a,n1, n2);
 
}

 

4、完整代码及其简单测试如下:

         main.c

 

#include <stdlib.h>
 
// 核心步骤:合并两个已排序的子数组
void CoreMerge(int* a, int n1, int n2) {
 
         //获取子区间各自的长度
         int*a1 = (int*)malloc(n1*sizeof(int));
         int*a2 = (int*)malloc(n2*sizeof(int));
 
         //拷贝子区间数据到缓冲区
         for(inti=0; i<n1; i++)
                   a1[i]= a[i];
         for(inti=0; i<n2; i++)
                   a2[i]= a[n1+i];
 
         //依次比较缓冲区数据大小填入原数组
         inti=0, j=0, k=0;
         for(;i<n1&&j<n2; k++) {
                   if(a1[i]<a2[j]){
                            a[k]= a1[i];
                            i++;
                   }else {
                            a[k]= a2[j];
                            j++;
                   }
         }
 
         //将某一个数组中未取完的数据填入剩余空间
         if(i==n1){
                   for(;j<n2; k++) {
                            a[k]= a2[j];
                            j++;
                   }
         }else {
                   for(;i<n1; k++) {
                            a[k]= a1[i];
                            i++;
                   }
         }
 
         //释放缓冲区
         free(a1);
         free(a2);
}
 
// 递归函数
void MergeSort(int* a, int n) {
        
         //空指针或长度小于1
         if(!a|| n<2) {
                   return;
         }
 
         //递归左右子数组
         intn1 = n/2, n2 = n-n1;
         MergeSort(a,n1);
         MergeSort(a+n1,n2);
 
         //合并两个已排序子数组
         CoreMerge(a,n1, n2);
 
}
 
#include <stdio.h>
 
int main() {
 
         inta[] = { 5, 7, 5, 2, 1, 6 };
         intn = sizeof(a)/sizeof(int);
 
         MergeSort(a,n);
 
         for(inti=0; i<n; i++) {
                   printf("%d\n",a[i]);
         }
 
         while(1){} // VC调试
         return0;
}

点赞