1.快速排序

原理

略过,网上太多了

代码

#include<iostream>
using namespace std;

const int N = 1e5 + 10;

int s[N];

void quickSort(int l, int r){
    if(l >= r) return;
    int mid = s[(l + r) >> 1];
    int i = l - 1, j = r + 1;
    while(i < j){
        do ++i; while(s[i] < mid);
        do --j; while(s[j] > mid);
        if(i < j) swap(s[i], s[j]);
    }
    quickSort(l, j);
    quickSort(j + 1, r);
}

int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i++) cin >> s[i];
    quickSort(0, n - 1);
    for(int i = 0; i < n; i++) cout << s[i] << " ";
    return 0;
}

接收输入格式如下,第一行是长度,第二行是具体数字,以空格分隔

4
3 1 2 4

Q&A

1. 为什么是先do后while?

while(i < j){
	do ++i; while(s[i] < mid);
	do --j; while(s[j] > mid);
	if(i < j) swap(s[i], s[j]);
}
能不能改成
while(i < j){
	while(s[i] < mid) ++i;
	while(s[j] > mid) --j;
	if(i < j) swap(s[i], s[j]);
}

考虑如下情况

8 8 8

当值都相同时,ij会停止移动,导致死循环,先do后while能保证每轮循环指针都至少移动一次,从而跳出循环。

2. 最后两步划分区间时,能使用i进行划分吗?

即,将

quickSort(l, j);
quickSort(j + 1, r);

改成

quickSort(l, i);
quickSort(i + 1, r);

答:不能

在快排中,每一步都需要将数据划分成的两个子数组,且左边子数组的每个元素都小于右边的 --条件1

在运行结束时

  1. i的含义是,s[l..i-1] <= mid
  2. j的含义是,s[j+1...r] >= mid

所以在执行结束时,s[i] > mid, s[j] < mid

所以 s[l,j] <= mid、s[j+1,r] >= mid,满足 条件1,划分合理

而因为 s[i] > mid,所以 s[l,i]并不满足小于等于mid,虽然 s[i+1,r] >= mid成立,但还是不满足 条件1,所以不能使用i进行划分

3. 如何使用i进行划分?

根据条件1,可以使用i划分为 [l, i-1][i, r],此时就满足条件了

但需要将mid的求法改为

int mid = s[(l+r+1)>>1];

为什么?先看下 int mid = s[(l+r) >> 1]时,出错的案例:

2
1 2
# 1
	[0,1] mid = 0
	执行结束时:i = 0, j = 0
	划分为[0,-1]和[0,1]
# 2.1
  [0,-1], 返回不执行
# 2.2
	[0,1], mid = 0
	执行结束时:i = 1, j = 0
	划分为[0,-1]和[0,1]
重复第一轮循环

死循环的原因是,当数组有序,且较小时,mid = s[(l+r)>>1] = l,此时i和j会都移动到mid,即l

此时 [l, i-1][i, r]就等于 [l, l-1][l, r],从而陷入死循环

解决方法就是保证i不能移动到l,即mid不能等于l

int mid=(l+r+1)>>1是向上取整,此时 mid = r,从而避免死循环

以r分割时也是同理,当数组有序,且数组较小时,mid = s[(l+r+1)>>1] = r,此时i和j会都移动到r,

此时 [l, j][j+1, r]就等于 [l, r][r+1, r],从而陷入死循环

解决方法就是保证j不能移动到r,即mid不能等于r

int mid = (l+r)>>1是向下取整,此时 mid = l,从而避免陷入死循环

关于边界问题,二分算法也有同样的问题和同样的解决思

略过,网上太多了

代码

#include<iostream>
using namespace std;

const int N = 1e5 + 10;

int s[N];

void quickSort(int l, int r){
    if(l >= r) return;
    int mid = s[(l + r) >> 1];
    int i = l - 1, j = r + 1;
    while(i < j){
        do ++i; while(s[i] < mid);
        do --j; while(s[j] > mid);
        if(i < j) swap(s[i], s[j]);
    }
    quickSort(l, j);
    quickSort(j + 1, r);
}

int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i++) cin >> s[i];
    quickSort(0, n - 1);
    for(int i = 0; i < n; i++) cout << s[i] << " ";
    return 0;
}

接收输入格式如下,第一行是长度,第二行是具体数字,以空格分隔

4
3 1 2 4

Q&A

1. 为什么是 do ++i; while(s[i] < mid);而不是先 while

考虑如下情况

8 8 8

当队列值都相同时,ij会停止移动,导致死循环,而先do再while会保证每轮循环指针都会移动,从而跳出循环

2. 边界必须是 [l,j][j+1,r]吗?

也可以以l为边界,修改为 [l, i-1][i, r],代码如下

void quickSort(int l, int r){
    if(l >= r) return;
    int mid = s[(l + r + 1) >> 1];
    int i = l - 1, j = r + 1;
    while(i < j){
        do ++i; while(s[i] < mid);
        do --j; while(s[j] > mid);
        if(i < j) swap(s[i], s[j]);
    }
    quickSort(l, i - 1);
    quickSort(i, r);
}

2.1为什么要变成 [l, i-1][i, r]

快排的原理是每轮执行完后,分治时,左边的都小于等于mid,而右边的都大于等于mid

i的含义是,在运行结束时,有 s[l..i-1] <= mid

j的含义是,s[j+1...r] >= mid

所以 s[i]是可能大于 mid的,如果用 [l, i][i+1, r],就会出错,因为违反了定义。

2.2 为什么mid的取值方法是 (l+r+1)>>1?

若没有+1,举一个错误案例

4
3 1 2 4
# 一
	l = 0, r = 3, mid = 1
	执行结束时:
	1 3 2 4, i = 1, j = 0
# 二
  l = 0, r = 0, 返回不执行
# 三
	l = 1, r = 3, mid = 2
	执行结束时:
	2 3 4, i = 2, j = 1
# 四
	l = 1, r = 1, 返回不执行
# 五
	l = 2, r = 3, mid = 3
	执行结束时:
	3 4, i = 2, j = 2
# 六
	l = 2, r = 1, 返回不执行
# 七
	l = 2, r = 3, mid = 3
	执行结束时:
	3 4, i = 2, j = 2
# 八
	l = 2, r = 1, 返回不执行

从第五轮开始了死循环,原因是递归后,左右边界没有变化 (2,3),导致无限递归

以l分割时,当mid取到 s[l],且数组有序时,i此时只会移动一次,等于l,而j会一直减到i,等于l,然后停止。此时 (i,j)就是 (l,r),所以要做的就是让 mid的取值不要等于 s[l],而 (l+r+1)>>1是向上取整,只要 l!=r,mid就永远不会取到l,保证了区间一直在改变

以r分割时也是同理,(l+r)>>1是向下取整

posted @ 2023-06-20 23:21  INnoVation-V2  阅读(7)  评论(0编辑  收藏  举报