高效的归并排序与快速排序写法

1,204 阅读2分钟

归并排序

此代码为邓俊辉数据结构课堂归并排序代码,个人感觉很直观而且节省空间。

//
// Created by Lone Kriss on 2021/3/17.
//

#include <iostream>
#include <vector>
using namespace std;

void mergeSort(vector<int>& nums, int lo, int mi, int hi)
{
    int i =0, j = 0, k = 0;
    int* A = &nums[lo];
    int lb = mi - lo;
    int *B = new int[lb];
    int *C = &nums[mi];
    int lc = hi - mi;
    for(int i = 0;i < lb; i++)
    {
        B[i] = A[i];
    }

    while(j < lb && k < lc)
    {
        A[i++] = (B[j] <= C[k]) ? B[j++] : C[k++];
    }
    while(j < lb)
    {
        A[i++] = B[j++];
    }
    delete[] B;
}

void merge(vector<int>& nums, int lo, int hi) //[lo,hi)
{
    if( hi - lo < 2)  return ;
    int mi = (lo + hi) / 2;
    merge(nums, lo, mi);
    merge(nums,mi,hi);
    mergeSort(nums, lo, mi, hi);
}
int main()
{
    vector<int> nums{2,6,1,3,5,4,7,9,8};
    merge(nums,0, nums.size());
    for(auto c: nums)
    {
        cout<< c <<' ';
    }
}

快排

该版本为 坐在马桶学算法 博客介绍的算法。

缺点在于每次选取左边界为排序基准点。

我们如果每次选取的基准恰好把数据分成0,N-1,则会倒是退化成o(N^2)。

//
// Created by Lone Kriss on 2021/3/17.
//

#include <iostream>
#include <vector>
using namespace std;

void quickSort(vector<int>& nums, int lo, int hi)   //[lo,hi)
{
    if(lo >= hi) return ;  //递归终别忘了
    int base = nums[lo];

    int _lo = lo,_hi = hi - 1;

    while(_lo <  _hi)
    {
        while(nums[_hi] >= base && _lo <_hi)
        {
            _hi--;
        }
        while(nums[_lo] <= base && _lo <_hi)
        {
            _lo++;
        }
        swap(nums[_hi], nums[_lo]);
    }
    swap(nums[lo], nums[_lo]);


    quickSort(nums,lo,_lo);
    quickSort(nums, _lo+1,hi);
}



int main()
{

    vector<int> nums{2,6,1,3,5,4,7,9,8};

    quickSort(nums,0, nums.size());

    for(auto c: nums)
    {
        cout<< c <<' ';
    }
}

acwing版本:

#include <algorithm>
#include <iostream>

using namespace std;

void quick_sort(int q[], int l, int r)
{
    if (l >= r) return;

    int i = l - 1, j = r + 1, x = q[l + r >> 1];
    while (i < j)
    {
        do i ++ ; while (q[i] < x);
        do j -- ; while (q[j] > x);
        if (i < j) swap(q[i], q[j]);//记住判断
    }
    quick_sort(q, l, j), quick_sort(q, j + 1, r); //边界
}


int main(){
    int N;
    cin >> N;
    int nums[100010];
    for(int i= 0;i<N;i++){w
        scanf("%d",&nums[i]);
    }
    
    quick_sort(nums,0,N-1);
    //quickSort(nums,0,N-1);
    for(int i= 0;i<N;i++){
        cout << nums[i] <<' ';
    }
    
    return 0;
}

写法简洁,但是也避免不了如果中间值恰好把数据分成0,N-1.
如何避免
采用三数取中方法,在选取基准的时候选择左边界,中间,右边界之中的中间值作为基准,就可以避免最坏情况。
具体参考:
blog.csdn.net/liuyi120716…


对acwing排序过程中的一些疑惑
打印每一轮交换后的结果。可以看到第三轮base 49 并没有放它应该在的位置。 image-20210411095405069 根据定义:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。 那么第三轮迭代结果就不符合快排定义,因为49之后的31比49小却出现在了它左边。 但是该写法为什么又能保证最终结果是正确的? 以及为什么递归参数是j,j+1?

同时,如果使用该写法,因为选取的base 不一定放在了它本该在的位置上,所以类似 前k个最小数 就不能使用此写法了?

自问自答
之前思考受到了马桶法快排算法误导,认为选择的基数交换后一定会放在正确的位置上,实际上不是。 在每一轮迭代,数据被划分成2个区间[快排定义也说的很清楚是两个区间被排好序],小于等于基准的和大于基准的,例如上面第三轮[31 49 37] [88 97 68 54 59]. 而j恰好是闭区间划分点,所以参数就是j和j+1.

参考资料:
www.acwing.com/blog/conten…

www.cnblogs.com/zhoug2020/p…