归并排序(C++) -- 速度与空间有机结合

归并排序(C++) – 速度与空间有机结合

  归并排序也是一个O(nlogn)的算法,之所以称这个算法为速度与空间的有机结合,是因为这个算法既能做到O(nlogn)时间赋值度的效率,又能借助外部的空间进行排序,因为只有数据比较的时候需要将数据存入到主机中进行,别的时候数据都可以存储在外部存储中,当然这种情况下就多了很多数据的读取和写入,效率会有很大影响,但能力还是在的;所以要最大程度发挥出O(nlogn)算法的效率,还是要用内部的存储空间,且需要进行优化;算法实现分自顶向下和自底向上两种~~

  归并排序之所以能达到O(nlogn),主要用到了数据结构中的二分法,将庞大的数据拆分成两段,不断分不断分直到只有两个数据时进行比较,比较完合并,然后再两个数据和另外两个数据进行比较,比较完合并,然后再四个数据和另外四个数据进行比较,比较完合并,直至所有数据合并,对比于每个数据都要和别的数据比较一次的O(n2)方法,时间效率大大提升,当然是在数据量庞大的时候;

归并排序 – 自顶向下

  自顶向下用的就是递归,将大数组递归拆分两段到两个比较,因为递归的特性,思路和代码体现就是从大到小地进行递归,所以是自顶向下;

  实现中涉及到三个方法,从第三个方法mergeSortTopDown往上看,注意区分第二个方法是__mergeSortTopDown;

// 合并方法
template <typename T>
void __merge(T arr[], T arrTemp[], int L, int Mid, int RightEnd) {
    int i = L;
    int j = Mid+1;
    // k是排好序存入的临时数组的下标
    int k = L;
    // 左右两部分数据未遍历完时循环遍历
    while(i<=Mid && j<=RightEnd) {
        // 左边时L至Mid,右边时Mid+1至RightEnd
        // ++放在后面,是先赋值,再使k、i加1
        if(arr[i]<arr[j]) arrTemp[k++] = arr[i++];
        else arrTemp[k++] = arr[j++];
    }
    // 上面while循环结束后,左边部分或右边部分可能会有剩余未比较的(因为当其中一部分比较完了就退出循环了),
    // 需要将剩余的数据加入到临时数组中
    while(i <= Mid) {
        arrTemp[k++] = arr[i++];
    }
    while(j <= RightEnd) {
        arrTemp[k++] = arr[j++];
    }
    // 将临时数组中已有序部分复制到原数组,使之这部分有序
    for(int tail=L;tail<=RightEnd;tail++) {
        arr[tail] = arrTemp[tail];
    }
}

// 实际递归调用的归并排序方法
template <typename T>
// L为拆分出来的左边界,RightEnd为右边界
void __mergeSortTopDown(T arr[], T arrTemp[], int L, int RightEnd) {
    // 判断是否只有一个数据,不是时递归进行二分拆分,排序后归并,如果已经只有一个数据(L=RightEnd)则不用操作
    if (L < RightEnd) {
	    int Mid = (L + RightEnd) / 2;
	    // 二分后左右边部分递归
	    __mergeSortTopDown(arr,arrTemp,L,Mid);
	    __mergeSortTopDown(arr,arrTemp,Mid+1,RightEnd);
	    __merge(arr,arrTemp,L,Mid,RightEnd);
    }
}

// 归并排序,递归玩法,这种玩法时自顶向下的
// 这个方法主要是为了统一接口,方便测试和调用
template <typename T>
// mergeSortTopDown提供统一接口,__mergeSort为实际排序方法
void mergeSortTopDown(T arr[], int n) {
    int L = 0;
    int RightEnd = n-1;
    // arrTemp是用于归并时临时存储有序结果的数组,之所以在这里创建因为空间的申请和释放需要资源和时间,
    // 归并排序会频繁进行数据归并操作,在主函数中申请空间后直接传递指针,减少了这部分时间
    T* arrTemp = new T[n];

    __mergeSortTopDown(arr,arrTemp,L,RightEnd);

    delete[] arrTemp;
}

自顶向下归并排序的优化实现:

  • 数据量小于等于20个时用插入排序:所有算法不管是O(n2)还是O(nlogn)前面都会有一个常数(如O(Anlongn),A是一个常数),只是当n很大时,这个系数被忽略不计了,当这n较小时,这个系数就有影响了,归并排序前面常数较大,所以当用复杂算法排好大部分数据后改用插入排序(特别是n较小时序列基本有序的可能性也大了)进行排序;
  • 因为进行归并时,左边和右边的两部分都是已经有序的,所以如果左边部分的右边界数据已经小于右边部分的左边界数据,那就没必要再进行归并操作了,所以可以加if语句进行判断,这样对于基本有序序列能大大提高效率;然而对于随机序列,如果每次if都是在做无用功,那么if语句带来的耗时可能就降低效率了;
  • 接口方法和合并方法没有进行改动,复用上面的;
// 实际递归调用的归并排序方法
template <typename T>
// L为拆分出来的左边界,RightEnd为右边界
void __mergeSortTopDown(T arr[], T arrTemp[], int L, int RightEnd) {
    // 优化一:当需要排序的数据小于等于20个时用插入排序
    if(RightEnd-L<=20) {
        insertSortTool(arr,L,RightEnd);
        return;
    }
    // 判断是否只有一个数据,不是时递归进行二分拆分,排序后归并,如果已经只有一个数据(L=RightEnd)则不用操作
    //if (L < RightEnd) {
    int Mid = (L + RightEnd) / 2;
    __mergeSortTopDown(arr,arrTemp,L,Mid);
    __mergeSortTopDown(arr,arrTemp,Mid+1,RightEnd);
    // 优化二:边界判断
    // 因为左边部分和右边部分都已有序,只有当左边的右边界大于右边的左边界时才有必要进行归并
    // 只有当处理的数据有可能近乎有序时才加这个,因为if语句也是额外损耗
    if(arr[Mid] > arr[Mid+1])
        __merge(arr,arrTemp,L,Mid,RightEnd);
    //}
}

归并排序 – 自底向上

  自底向上用的时回溯,回溯在内存使用上要比递归少,因为递归中每一次递归使用自己的方法都要占据内存;这里回溯用到了for语句的嵌套使用;与自顶向下相反,从1个数据一组开始,也就是一开始两个数据比较,然后乘2,以此类推;自底向上不需要统一接口,直接调用就可以了,另外合并的方法与上面一样,不再重复列出

// 归并排序,自底向上玩法
template <typename T>
void mergeSortBottomUp(T arr[], int n) {
    T* arrTemp = new T[n];
	// 从1个数据一组开始
    for(int i=1;i<n;i*=2)
    	// 按每个间隔为一组相互比较,然后合并成原来2倍的数据量
        for(int j=0;j<n-i;j+=i*2)
        	__merge(arr,arrTemp,j,j+i-1,min(j+2*i-1,n-1));

    delete[] arrTemp;
}

自底向上归并排序的优化实现:优化思路和自顶向下一样,也是数据量小时先用插入排序然后,边界先比较再决定是否要进行归并

// 归并排序,自底向上玩法
// 优化过后的自底向上和自顶向下性能差不多
template <typename T>
void mergeSortBottomUp(T arr[], int n) {
    T* arrTemp = new T[n];
    // 优化一:插入排序对每20个数据进行排序,让每20个数据都是有序的,给下面的归并排序做准备
    for(int i=0;i<n;i+=20) {
        insertSortTool(arr,i,min(i+20,n-1));
    }
    // 从20个数据一组开始
    for(int i=20;i<n;i*=2)
        for(int j=0;j<n-i;j+=i*2)
        	// 优化二:边界判断
            if(arr[j+i-1]>arr[j+i])
                __merge(arr,arrTemp,j,j+i-1,min(j+2*i-1,n-1));

    delete[] arrTemp;
}

插入排序与归并排序性能PK赛

比赛规则:

  1. 对100万个数据进行排序;
  2. 分随机数序列和基本有序序列两场比赛;
  3. 基本有序序列的未有序数据数量是200个;
  4. 所有排序法均已优化;
  5. 从上到下是插入排序、自顶向下归并排序、自底向上归并排序;
随机数序列:
insertSort:530.557
mergeSortTopDown:0.135
mergeSortBottomUp:0.138
基本有序序列:
insertSort:0.007
mergeSortTopDown:0.005
mergeSortBottomUp:0.004

比赛结果:

  • 毫无疑问,在面对随机的100万个数据时,O(logn2)的插入排序和O(nlongn)的归并排序不在一个level上;但是面对基本有序序列时,插入排序可以说扳回了一城;另外归并排序在数据量低时用到了插入排序,所以说插入排序虽然在面对随机的大量数据时无法作为主排序方法,但是打辅助还是杠杠的;
  • 优化过后的自顶向下和自底向上耗时差不多,没有太多差别;但是自顶向下因为用的递归,占用内存较大;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值