常用排序算法--合并排序和快速排序

常用排序算法——合并排序

分治

分治(Divide and Conquer)是一场常见的算法策略。分治策略的基本思想就是对于一个问题规模为N的问题,将其划分为规模足够小的K个子问题,子问题由于规模足够小可以直接求解,最后将规模足够小的K的问题的解合并得出原问题的解。
分治策略的问题的求解过程的一般套路就是:

  1. 判断问题规模,足够小进入步骤2,否者进入步骤3
  2. 直接对问题进行求解,返回问题的解。
  3. 将问题拆分成K个小规模的问题,进入步骤2。并将解合并,得到原问题的解。

由于分治策略的步骤描述自带递归属性,因此分治策略的算法实现常采用递归来实现。递归的算法通常会含有三部分,第一部分是个基准情况(Base Case),第二部分是问题拆分,并调用自身,第三部分是合并并返回结果。

分治策略中主要有两个关键过程:

  • 将原问题拆分Division
  • 将各子问题合并Combination

分治策略的主要时间开销发生在这两个过程,通常分治策略有两类

  1. 拆分容易,合并困难
  2. 拆分困难,合并简单

下文中的合并排序快速排序分别属于上面的第1和第2类。

合并排序和快速排序

在常见排序算法中,合并排序和快速排序是典型的分治算法,其时间复杂度的平均性能均为nlgn。合并排序的最坏时间复杂度也能保持nlgn,而快速排序的最坏时间复杂度却为n*n合并排序是稳定的,而快速排序不是稳定的。另外合并排序是拆分容易,合并困难,而快速排序则是拆分困难,合并容易。

合并排序又称归并排序,主要的思想是:将待排序列拆分至数个足够小的子序列,然后将相邻子序列合并为一个有序子序列,重复合并相邻有序子序列直到整个序列有序。

快速排序的主要思想则是,将待排序列拆分为左右两个子序列A,B,使得子序列A的元素都小于子序列B,然后对子序列A,B继续进行这种拆分,直到待拆分的序列足够小,最后整个序列变成有序。

由于合并排序和快速排序每次元素的移动都不只移动了一个位置,因此每次元素的比较都不只消除了一个逆序对,因此对于插入排序这种每次比较只移动一个位置的算法,时间复杂度会得到改善。

快速排序的递归实现

int partition(int* a, int l, int h) {
    int pivot = a[l];
    int i,j;
    i = l - 1;
    j = h + 1;
    while(true) {
        while(a[--j] > pivot);
        while(a[++i] < pivot)
            if(i >= h)
                break;
        //i 、j cross
        if(i >= j)
            break;

        int tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
    a[j] = pivot;
    return j;
}

void quickSort(int* a, int l, int h) {
    if(l >= h)
        return;
    int p = partition(a, l, h);
    quickSort(a, l, p);
    quickSort(a, p+1, h);
}

合并排序的递归实现

void merge(int* a, int l, int m, int h) {
    if(l == h)
        return;

    int* b = (int*)malloc(sizeof(int) * (h - l + 1));
    int i = l, j = m + 1;
    int k = 0;

    while(i <= m && j <= h)
        b[k++] = a[i] > a[j] ? a[j++] : a[i++];

    if(i <= m)
        while(i <= m)
            b[k++] = a[i++];
    if(j <= h)
        while(j <= h)
            b[k++] = a[j++];
    //回写
    i = l;
    k = 0;

    for(; i <= h;)
        a[i++] = b[k++];
}

void mergeSort(int* a, int l, int h) {
    if(l >= h)
        return;
    int m = (h + l) / 2;
    mergeSort(a, l, m);
    mergeSort(a, m+1, h);
    merge(a, l, m, h);
}

合并排序的非递归实现

由于递归会导致函数调用栈的暴增,会引入额外的时间开销,例如现场保护和恢复之类的。通递归版本的算法都可以改写为迭代版本的算法。递归算法的优点是实现简单直观,易于理解,缺点是如果有时候调用深度过深,会带来栈内存溢出和额外的运行时间开销。迭代算法的有点是不会有额外的函数调用栈的增长, 缺点是难于理解且难于实现。

合并排序的迭代版本的实现相对来讲是比较简单直观的,大致思路就是:第1轮,待排序列S中相邻的长度为1的子序列进行合并,第2轮,序列S中相邻的长度为2的子序列进行合并,第i轮,待排序列S中相邻的长度为2的i-1次方的子序列进行合并。直到待合并的子序列数为1。

void merge(int* a, int l, int m, int h) {
    if(l == h)
        return;

    int* b = (int*)malloc(sizeof(int) * (h - l + 1));
    int i = l, j = m + 1;
    int k = 0;

    while(i <= m && j <= h)
        b[k++] = a[i] > a[j] ? a[j++] : a[i++];

    if(i <= m)
        while(i <= m)
            b[k++] = a[i++];
    if(j <= h)
        while(j <= h)
            b[k++] = a[j++];
    //回写
    i = l;
    k = 0;

    for(; i <= h;)
        a[i++] = b[k++];
}

//非递归版本
void mergeIteation(int* a, int n) {
    int offset = 1;
    while(offset <= n) {
        int i = 0;
        for(;i < n;) {
            merge(a,i, i+offset-1,i+offset*2 - 1);
            i = i + offset * 2;
            if(n - 1 - i < offset)
                break;
        }
        offset *= 2;
    }
}
  • 6
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
合并排序快速排序都是使用分治思想实现的排序算法,并且都可以使用递归来完成。 它们的区别主要体现在以下几个方面: 1. 时间复杂度: 快速排序的时间复杂度是O(nlog2n),而合并排序的时间复杂度也是O(nlog2n)。不过,平均来说,快速排序是最好的一种内部排序方法。 2. 空间使用: 快速排序是原地排序,而合并排序不是原地排序。合并排序需要额外的空间来协助合并两个有序数组。 3. 分治思路: 快速排序每次将数组一分为三(将排好序的那个数字摘出来),而合并排序每次将数组一分为二。 4. 排序方向: 合并排序是自上而下的分解,接着再自下而上的合并排序。而快速排序是边分解边排序,是自上而下的排序。 关于合并排序快速排序的具体实现,我发现了一个C++的代码示例,其中使用了快速排序的思想,但是在快排中采用了二分地只排前n个的方法,以在n的复杂度内搞定排序。你可以参考这个示例来理解具体的实现过程。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [算法:C++ 实现 快速排序归并排序](https://blog.csdn.net/weixin_44775255/article/details/124063700)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [【C++】快速排序以及归并排序算法总结及思想应用分析](https://blog.csdn.net/m0_57298796/article/details/127426894)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值