递归、非递归的归并排序及其优化

归并排序又叫合并排序,是用分治策略对n个元素进行排序的算法。

将待排序的元素分成大小大致相同的两个子集合(这两个子集合都是有序的),对两个子集合经行排序,最终将排好序的子集合合并成为所需要的排好序的集合

该排序最主要的为合并left-mid,mid+1-right两个子集合的合并算法

void Merge(int* dest, int* src, int left, int mid, int right)
{
    int i = left, j = mid + 1;//i是左子集合的第一个元素下标,j是右子集合的第一个
    int k = left;//k是目标集合的下标
    while (i <= mid && j <= right)
    {
        dest[k++] = src[i] < src[j] ? src[i++] : src[j++];
    }
    while (i <= mid)//左子集合没有遍历完
    {
        dest[k++]  = src[i++];
    }
    while (j <= right)//右没有遍历完
    {
        dest[k++] = src[j++];
    }
}

得到一个乱序数组时,先分再排——一分为二,分到只有一个数时为有序,因为合并是合的“有序”数组,所以这时候就可以递归的使用合并算法进行排序

递归分解

void MergePass(int* tmp,int* nums, int left, int right)
{
    if (left < right)
    {
        int mid = left+ ((right - left) >> 1);
        MergePass(tmp, nums, left, mid);//分左边
        MergePass(tmp, nums, mid + 1, right);//分右边
        Merge(tmp, nums,left,mid,right);//分完有序了合并
        Copy(nums, tmp, left, right);
    }
}

完整代码

void Merge(int* dest, int* src, int left, int mid, int right)
{
    int i = left, j = mid + 1;//i是左子集合的第一个元素下标,j是右子集合的第一个
    int k = left;//k是目标集合的下标
    while (i <= mid && j <= right)
    {
        dest[k++] = src[i] < src[j] ? src[i++] : src[j++];
    }
    while (i <= mid)//左子集合没有遍历完
    {
        dest[k++]  = src[i++];
    }
    while (j <= right)//右没有遍历完
    {
        dest[k++] = src[j++];
    }
}
void Copy(int* dest, int* src, int left, int right)
{
    for (int i = left; i <= right; i++)
    {
        dest[i] = src[i];
    }
}
void MergePass(int* tmp,int* nums, int left, int right)
{
    
    if (left < right)
    {
        int mid = left+ ((right - left) >> 1);
        MergePass(tmp, nums, left, mid);//分左边
        MergePass(tmp, nums, mid + 1, right);//分右边
        Merge(tmp, nums,left,mid,right);//分完有序了合并
        Copy(nums, tmp, left, right);
    }
}
void MergeSort(int* nums, int n)
{
    if (n < 2 || nums == nullptr)return;
    int* tmp = new int[n];
    MergePass(tmp,nums, 0, n-1);
    delete[]tmp;
}

非递归(自下而上)

递归和非递归的区别在于分解的函数和排序函数。

非递归时第一轮传入s=1,将nums里的数组分为一个数一块,利用归并函数归并,i控制归并的范围,如下图:56和23归并,50和45归并,在while中将所有数都归并到tmp中时,退出MergePass分解函数;因为上一轮合并了两个元素为一组有序子集合,第二轮开始s = 2,将tmp中的数组再用分解、合并函数,之后倒回nums中,以此循环,直到所有数都有序。

分解函数中使用i保存两个需要归并的子集合中,左子集开始位置的下标,i每次往前移动2*s-1(s:每个子集的数据个数)到下一对需要合并的子集开始位置。

合并到最后时不一定是2^n(左右子集合数量相对)个,不能再用之前的i控制的范围(第六行)合并,while时注意i位置判断i+2*s-1<=n-1时才能一对相同长度子数组合并,化简成i<=n-2*s,如果右子序列的元素个数比左子序列少,另外使用一个合并函数将传入的right范围改成n-1,即数组末尾。如果最后一组只有左子集合,没有右子集合,则将左子集合直接放入排好的数组中(左子集合是有序的)

void MergePass(int* tmp, int* nums, int n,int s)
{
    int i = 0;
    while (i+2*s-1<=n-1)//i再往后走一个待归并的长度还在数组范围内,i<=n-2*s
    {
        Merge(tmp, nums, i, i+s-1,i+s*2-1);
        i += s * 2;
    }
    if(n-1>=i+s)//尾部剩余有数据可以和前面一块归并
        Merge(tmp, nums, i, i + s - 1,n - 1);//右子序列的元素个数比左子序列少
    else//后面没有数据,不产生归并操作,直接装到目标数组中
    {
        for(int j = i;j<n;j++)
        {
            tmp[i]=nums[i];
        }
    }

}
void MergeSort(int* nums, int n)
{
    if (n < 2 || nums == nullptr)return;
    int* tmp = new int[n];
    int s = 1;
    while(s<n)
    {
        MergePass(tmp, nums, n, s);
        s += s;
        MergePass(nums,tmp, n, s);
        s += s;
    }
    delete[]tmp;
}

优化方法

  1. 设置一个阀值,当划分到的子集合较小时,直接使用插入排序或快速排序替代归并。

//Mergesort中
const int cutoff = 10;
if(right<=left+cutoff+1)
{
    QuickSort(nums,left,right);
    return;
}
  1. 如果已经排序好了,就不用排序了(即两个待排序的子集合中,左子集合的最右边的值<右集合中最左边的值)

if(muns[mid]<=nums[mid+1])return;

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

曦樂~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值