算法 - 快速排序和归并排序

AcWing学习记录
钱都花了不学也太亏了

两种排序算法

一、快排

快排的思路:

  • 确定分界值(一般选择q[(l + r) / 2])
  • 双指针扫描
  • 递归处理左右两段

注:
1.一次快排的结果,就是比q[l + r >> 1]小的数都在左边,比q[l + r >> 1]大的数都在右边。
根据这样的结果可以实现一个“分类“,记得大二数据结构的期末考题就是用O(nlog(n))的算法实现对给出的字母序列进行大小写字母划分。这道题的思想就是快排。

2.双指针扫描的过程就是让 i 和 j 分别指向数组首尾,分别向中间扫。i 遇到比分界值大的就停止,j 遇到比分界值小的就停止,然后 i 和 j 指向的数值交换,并继续往后扫,直到i >= j
为什么这样的过程就可以实现分类呢?因为在这样的过程下 i 左边的数一定比分界值小,j 右边的数一定比分界值大。i 和 j 相遇后,自然就实现了一次划分。这就是优雅的原理。

3.给一个例子:
4 3 2 1
假设我们取分界值为3,那么 i 、j 一开始分别指向4和1。4比3大,i 停下。1比3小,j也停下。此时 i 小于 j ,所以 i 和 j 互换,结果是1 3 2 4
因为 i 仍小于 j,所以继续:i 指向1,比3小,i++;i指向3,不比3小,i停下。j指向4,比3大,j–; j指向2,比3小,j停下。此时 i 小于 j ,所以 i 和 j 互换,结果是1 2 3 4
因为 i 仍小于 j,所以继续:i指向2,比3小,i++; i指向3,不比3小,i停下。j指向3,不比3大,j停下。此时 i 已经大于 j,不互换。
因为 i 大于 j,所以可以停了, 一次快排的结果是1 2 3 4(一次就排好实属巧合)。
(板子中是do while,是先++或–再判断。和上面表述略有一点点不同。)
快排板子如下:

void quick_sort(int q[], int l, int r){
    if(l >= r) return;
	//确定分界值
	int x = q[l + r >> 1];
	//双指针
    int i = l - 1, j = r + 1;

    while(i < j){
        do i++; while(q[i] < x); //先+1再判断
        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);
}

AcWing 785 代码如下:

#include<iostream>

using namespace std;

const int N = 100010;

int n;
int q[N];

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); //先+1再判断
        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(){
    scanf("%d", &n);
    for(int i = 0; i < n; i++) scanf("%d", &q[i]);

    quick_sort(q, 0, n-1);

    for(int i = 0; i < n; i++) printf("%d ", q[i]);

    return 0;

}

当然,上面那道题在main函数里直接用下面的代码也是没问题的。

sort(q, q+n)

二、归并排序

归并排序的思路:

  • 确定分界点(一般选用(l + r) / 2)(快排是确定分界值
  • 递归排序左边右边
  • 归并(合二为一)

注:
1.归并的原理是这样的:(也是优雅的双指针)
两个有序序列,两个指针分别指向序列首部。
每次比谁小,谁小就取谁出来,到一个新的数组中。这样两个指针各扫一遍,出来的新数组就是想要的新有序序列。

2.快排是整体先排,然后小的再排。而归并排序是每一次归并的两个序列必须是有序的,因此归并排序需要从小的序列开始。这在代码段中会有所体现。

3.给一个例子:
4 3 2 1:处理4 3、2 1。
4 3:处理4、3,顿号前后已经是一个数,不用再递归了,根据注1的原理排序,结果是3 4
2 1:处理2、1,顿号前后已经是一个数,不用再递归了,根据注1的原理排序,结果是1 2

处理4 3、2 1的结果是3 4、1 2,注意观察顿号前后分别是两个有序序列(递归处理好的结果),根据注1的原理排序,结果是1 2 3 4
感觉说的很乱,建议画图,递归这里不太好说清楚。
归并排序板子如下:

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

    int mid = l + r >> 1; //分界点

    merge_sort(q, l, mid);
    merge_sort(q, mid + 1, r);

    int k = 0;
    int i = l, j = mid + 1; //双指针 
    while(i <= mid && j <= r){
        if(q[i] <= q[j]) t[k ++] = q[i ++];
        else t[k ++] = q[j ++];
    }
    while(i <= mid) t[k ++] = q[i ++];
    while(j <= r) t[k ++] = q[j ++];

    for(i = l, j = 0; i <= r; i++, j++) q[i] = t[j];

}

AcWing 788 逆序对的数量 代码如下:
这道题我的理解:
归并排序的思路就是两个有序数组归并的过程,谁小谁先进。
如果前面的先进就不存在逆序;
如果后面的先进,前面有多少个没进就有多少个逆序对。

#include<iostream>

using namespace std;

const int N = 100010;

int n;
//int res = 0;
long long res = 0;
int q[N], t[N];

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

    int mid = l + r >> 1;
    merge_sort(q, l, mid);
    merge_sort(q, mid + 1, r);

    int k = 0, i = l, j = mid + 1;
    while(i <= mid && j <= r){
        if(q[i] <= q[j]) t[k ++] = q[i ++];
        else{
            t[k ++] = q[j ++];
            res += mid - i + 1;
        }

    }

    while(i <= mid) t[k ++] = q[i ++];
    while(j <= r) t[k ++] = q[j ++];

    for(i = l, j = 0; i <= r; i ++, j ++) q[i] = t[j];

}

int main(){
    scanf("%d", &n);
    for(int i = 0; i < n; i++) scanf("%d", &q[i]);

    merge_sort(q, 0, n-1);

    //printf("%d", res);
    printf("%lld", res);

    return 0;
}

本质上就只需要在else语句(q[ i ] > q[ j ])后面加上这样一句就可以:

res += mid - i + 1;

下一个会整理二分。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值