程序员面试系列——合并排序(递归实现)

合并排序基本思想

合并排序,或者叫归并排序,在算法思想中属于分治法。对于一个需要排序的数组,合并排序把它一分为二,并对每个子数组递归排序,然后把这两个排好序的子数组合并为一个有序数组。

本文要介绍两种实现,都是基于递归(自顶向下)。这两种实现没有本质的差别,第二种只是为了节省内存,提高效率。

第一种实现

假设A数组是要排序的数组。
1. 把A数组的前半部分复制到B数组,把A数组的后半部分复制到C数组
2. 对B数组和C数组分别排序(递归);之后,B数组和C数组已经有序
3. 把数组BC合并,成为有序数组A(见函数__merge

C语言代码如下。


/*
 * 将两个有序数组b和c合并为一个有序数组a
 * b是第一个非降序数组的首地址,长度是len_b
 * c是第二个非降序数组的首地址,长度是len_c
 * a指向输出数组的首地址
 * 注意:a数组需要调用者分配空间,且a数组不能与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数组的长度

    while(i < len_b && j < len_c) //&&的优先级更低 
    {
        if(b[i] <= c[j])
            a[k++] = b[i++];
        else
            a[k++] = c[j++];
    }

    if(i == len_b) //说明b已经处理完
        memcpy(a+k, c+j, (len-k)*sizeof(int));
    else         //说明c已经处理完
        memcpy(a+k, b+i, (len-k)*sizeof(int));
}

上面的这个函数完成“合二为一”的工作,即把非降序的BC合并为非降序的A.

/*
 * a是数组首地址,数组长度为len
 * 将数组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);//为数组b和c分配空间
    if(temp == NULL)
    {
        printf("malloc failed\n");
        return;
    }

    memcpy(temp, a, len*sizeof(int));
    int *b = temp;
    int *c = temp+len_b; //以上三行完成了复制和拆分

    merge_sort(b,len_b); //递归调用,对数组b排序
    merge_sort(c,len_c); //递归调用,对数组c排序

    __merge(a, b, len_b, c, len_c); //合并b和c

    free(temp);

}

merge_sort这个函数才是归并排序的主函数。

为了更好地理解,我画了一幅示意图。此图体现出递归出口(即递归结束条件)是数组长度等于1.

这里写图片描述

merge_sort函数在设计上的不足之处是内存开销大,也费时间(malloc函数用多了会影响性能)。

因为要为数组B和数组C分配空间,然后分别排序(因为是递归,所以又要分配与释放),排序完毕后再把二者合并到A数组,此时数组B和数组C的空间才可以释放。

可否改进一下呢?可以看看第二种实现。

第二种实现

假设A数组是要排序的数组。
1. 先分配一段内存(这里叫temp),其大小和数组A一样大
2. 把A数组一分为二,前半部分称为B数组,后半部分称为C数组
3. 对B数组和C数组分别排序(递归);排序时,借助temp(借助的原因请看4和5)
4. 把数组BC合并为有序数组,合并的结果在temp中。(利用函数__merge
5. 把temp的内容拷贝到数组A
6. 释放temp

这样做的特点是一次分配,反复利用,最后释放。优点是省内存,效率高。

C语言代码如下。


/*
 *内部函数
 *temp是一段内存的首地址,由调用者分配和释放
 */
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; //把数组a原地拆分为b和c

    __merge_sort(b,len_b,temp); //对数组b排序,借助内存temp
    __merge_sort(c,len_c,temp); //对数组c排序,借助内存temp

    __merge(temp, b, len_b, c, len_c); //合并b和c到temp

    memcpy(a,temp,len*sizeof(int)); //复制temp到数组a
}

/*
 *这种写法只需要申请一次空间,节省内存,效率更高
 */
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); //此函数实现见上文

    free(temp); 
}

测试

注意,以上代码需要包含的头文件是

#include <string.h>   //memcpy函数用这个头文件
#include <stdlib.h>   //malloc和free函数用这个头文件

测试代码如下。

#include <stdio.h>

/*
 *此函数为了测试用,功能是打印数组
 *a是数组首地址
 *len是数组长度
 */
void print_array(int *a, int len)
{
    int i=0;
    for(i=0; i<len; ++i)
        printf("%d ",a[i]);
    printf("\n");
}


//测试函数
int main(void)
{
    int a[10]={9,8,5,3,2,9,-1,-1,7,1,};   //10个数

    merge_sort(a,10);  //按照非降序排列

    print_array(a,10);  //打印排序后的数组
    return 0;
}

运行结果是:

-1 -1 1 2 3 5 7 8 9 9

—–欢迎斧正—–


【参考资料】
《算法设计与分析基础(第3版)》(清华大学出版社)

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值