归并排序递归及非递归实现(自然合并排序)

一:归并排序

普通的归并排序递归实现比较简单,就不在此赘述,一看就懂。下面贴上代码。
#include <iostream>
#include <assert.h>
using namespace std;

template <typename T>
void merge(T arr[], const int start, const int middle, const int end, const int size)
{                             
    T *room = new T[size*2];    //归并排序虽然是O(nlgn)时间,但是需要者O(n)的空间
    assert(room != NULL);

    int left = start;          
    int right = middle+1;

    int i = 0;
    while(left <= middle && right <= end){    //按值由小到达copy进room数组中
        if(arr[left] < arr[right])
            room[i++] = arr[left++];
        else
            room[i++] = arr[right++];
    }   
                            
    while(left <= middle)         //程序退出循环只有两种可能,分别处理
        room[i++] = arr[left++];
    while(right <= end)
        room[i++] = arr[right++];

    for(int i=start,j=0; i<=end; ++i,++j)   //将排序完成的一串数copy回原数组
        arr[i] = room[j];

    delete []room;
}

template <typename T>
void merge_sort(T arr[], int start, int end)   //归并排序主函数,start和end分别为起始和终止的下标
{
    if(start < end){      //如果start=end直接返回,因为单个元素天然有序
        int middle = (int)((start+end)/2);    //求中点
        merge_sort(arr, start, middle);       //归并左半部分
        merge_sort(arr, middle+1, end);       //归并右半部份
        merge(arr, start, middle, end, end-start+1);     //合并两部分
    }   
}

template <typename T>
void print(T arr[], const int size)  //打印函数
{
    for(int i=0; i<size; ++i)
        cout<<arr[i]<<' ';
    cout<<endl;
}

int main()
{
    int array[] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}; 
    int len = sizeof(array)/sizeof(int);
    merge_sort(array, 0, len-1);
    print(array, len);
    return 0;
}

二:自然合并排序

这个排序是归并排序的变体,基于非递归实现。它是由小到大合并排序完成的子数组完成排序的。
我们可以首先将数组a中相邻元素两两配对。用合并算法将它们排序,构成n/2组成都为2的排好序的子数组段,然后再将它们排序成长度为4的排好序的字数组段,如此继续下去,直至整个数组排好序。

注意有两种特殊情况:
调用归并操作将相邻的一对子数组进行归并时,有些情况需要对最后一组进行特殊处理:
① 若最后子数组个数小于等于步长s,则最后一个子数组无须和其它子数组归并(即本趟轮空),直接拷贝  
② 若子文件个数为偶数,则要注意最后一对子文件中后一子文件的区间上界是n。
例如:            数组 : 5 4 3 2 1 1 2
第一次2个一组归并: 4 5    2 3    11    2   //第一步2个一组合并,s为1,最后一组只有2一个元素,直接轮空。也可以理解为子数组个数为奇数个,直接轮空
下一次4个一组归并 :   2 3 4 5    11 2        //此时s=2, 11 2的长度为3,大于s,但是小于2s,那么将11和2进行归并即可。
这两种情况代码体现在这里:
    if(i+s < n)
        merge(x, y, i, i+s-1, n-1);
    else{
        for(int j=i; j<n; ++j){
            y[j] = x[j];
        }
    }
其实,用通俗的话来说,就是前面的合并完了剩下的不足一个就直接轮空,够一点几个就把那一个和另外半个合并就行了!
全部代码如下:
#include <iostream>
#include <assert.h>
using namespace std;

template <typename T>
void merge(T x[], T y[], int start, int centre, int end)
{
    int left = start;
    int middle = centre;
    int right = middle + 1;

    while(left <= centre && right <= end){
        if(x[left] < x[right])
            y[start++] = x[left++];    //这部分就是简单的比较拷贝,只是和上面归并排序程序不同的是
        else                           //这部分是从start开始copy的,之前的归并排序每次从0开始copy
            y[start++] = x[right++];
    }   

    while(left <= middle)
        y[start++] = x[left++];
    while(right <= end)
        y[start++] = x[right++];
}

template <typename T>
void merge_pass(T x[], T y[], int s, int n)   
{
    int i = 0;
    while(i <= n-2*s){      //当i和n相差2s,说明存在可合并的子数组  //举例两元素数组a[] = {1, 2},i=0,同时s=1时,n-2*s=0此时等于i,可进行合并 
        merge(x, y, i, i+s-1, i+2*s-1);    //采用传递起始,中间,终点下标形式,需要-1,例如上面的数组,s=1时终点下标0+2*1-1=1
        i += 2*s;           //每次按步长走
    }   

    if(i+s < n) //如果前面子数组合并完成,还剩一组子数组,例如5,4,3,2,1,1,2步长为2时,54和32合并,剩余11大小满足子数组,就合并它和2
        merge(x, y, i, i+s-1, n-1);
    else{          //如果剩余元素为奇数且不满一组子数组时,直接copy,如5,4,3,2,1,两两合并,剩1,此时i=4,n=5,s=1,则i+s=n,直接拷贝
        for(int j=i; j<n; ++j){
            y[j] = x[j];  //从i开始直接copy
        }
    }   
}

template <typename T>
void merge_sort(T arr[], const int size)   //归并排序函数
{
    T *extra = new T[size];    //申请额外空间
    assert(extra != NULL);

    int step = 1;    //步长初值为1
    while(step < size){
        merge_pass(arr, extra, step, size);   //将arr数组copy至extra数组,底层实现具体排序
        step <<= 1;        //步长翻倍
        merge_pass(extra, arr, step, size);   //再次copy回arr数组
        step <<= 1;        //继续翻倍,相当于每次拷来拷去的过程同时进行归并排序
    }   
}

template <typename T>
void print(T *arr, const int size)
{
    for(int i=0; i<size; ++i)
        cout<<arr[i]<<' ';
    cout<<endl;
}

int main()
{
    int array[] = {10, 9, 30, 8, 7, 5, 4, 3, 2, 1}; 
    int len = sizeof(array)/sizeof(int);
    merge_sort(array, len);
    print(array, len);
    return 0;
}
*以上代码均经过验证

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值