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
当值都相同时,i
和 j
会停止移动,导致死循环,先do后while能保证每轮循环指针都至少移动一次,从而跳出循环。
2. 最后两步划分区间时,能使用i进行划分吗?
即,将
quickSort(l, j);
quickSort(j + 1, r);
改成
quickSort(l, i);
quickSort(i + 1, r);
答:不能
在快排中,每一步都需要将数据划分成的两个子数组,且左边子数组的每个元素都小于右边的 --条件1
在运行结束时
i
的含义是,s[l..i-1] <= mid
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
当队列值都相同时,i
和 j
会停止移动,导致死循环,而先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
是向下取整