快排:
之前一直久仰快排大名,今日终于决定学习一下(主要是c++直接sort多爽(滑稽))
直接原因是接触到一个叫BFPRT的算法,快排作为应该是它的前导知识,于是今天就手打了一个(假)快排,在打的过程中遇到了一些问题,于是写下了这篇日记(滑稽)
关于快排我再赘述一下:
就是一个数列中选取一个基准点,然后从左右向中间遍历,将大于它的放右边,小于它的放左边。
具体做法:
1.确定 l,r指针,以及flag基准点
2.r指针先动,r--,直到遇到一个数小于flag停下
3.l指针再动,l++,直到遇到一个数大于flag停下
4.交换l,r指针
5.重复上述操作直到l==r
6.交换基准点和l,r相遇那个位置的指针
7.递归调用上述过程。
疑问1: l,r移动过程中若是遇到一个数的值等于基准点怎么办,用不用带上它一起移到最后l,r相等的位置?
答:不用管,继续移动。因为处理好之后基准点两边的数分别是小于等于基准点,大于等于基准点,当下一次递归的时候等于基准点的值还是会被移动接近该基准点。
疑问2: 为什么要r先动,l后动?
答:应该和基准数的选择有关,在升序排序下,选左边那个,假如l先动,那么我们考虑这种情况: 5 4 3 6 7 8 ,l会在6那里停下来,然后r再动,在6那里被l逼停下来,交换6 和 5 ,结果: 6 4 3 5 7 8 ,就出错了。
因此应该选基准数相对的另外一边作为开始。
放代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 7;
int num[maxn];
void q_sort(int *beg,int *end) { //升序快排,含头不含尾
if (beg >= end) return;
int *l = beg, *r = end - 1;
int sto;
int flag = *beg; //基准点
while (l < r) {
while (*r >= flag&&r > l) r--; //降序快排的化这里符号变一下
while (*l <= flag&&l < r) l++;
sto = *r; //swap
*r = *l;
*l = sto;
}
sto = *beg;
*beg = *l;
*l = sto;
q_sort(beg, l); //含前不含后啊鸽鸽
q_sort(l + 1, end);
}
int main() {
int n; cin >> n;
for (int i = 1; i <= n; i++) cin >> num[i];
q_sort(num + 1, num + n + 1);
for (int i = 1; i <= n; i++) cout << num[i] << " ";
cout << endl;
}
BFPRT算法:
好的施工完毕
我发现用这个算法过不了我们学校oj上面一道用nth_element能过的题,感觉这个还是不够快啊!!!
这个算法可以解决无序数组中第k大的问题,因为使用partition的思想做得话,因为其基准点默认第一个数,因此存在一定情况会退化成O(n^2)的算法。
这个算法就解决了这个不稳定的问题,其通过自选基准点解决。
做法:
1.分组,每5个为一组
2.组内排序,组外不需要管
3.将每个组的中位数拿出来组另外一组,名字为qq
5.得到qq的中位数,用那个作划分
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 1e7 + 7;
int num[maxn], cnt[maxn];
inline int read() {
int x = 0, f = 1; register char ch = getchar();
for (; ch<'0' || ch>'9'; ch = getchar()) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
return x * f;
}
int get_mid(int l,int r) {//含前不含后
sort(num + l, num + r);
return num[(l + r) / 2];
}
int part_sort(int l,int r) { //分成五组然后sort
int flag = (r - l ) / 5 + (r - l ) % 5 == 0 ? 0 : 1;
// flag:究竟要分几组
int middle[maxn]; //中位数组成的数组
for (int i = 0; i < flag; i++) {
middle[i] = get_mid(l + i * 5, min(l+ i * 5 + 5, r));
}
sort(middle, middle + flag);
return middle[flag / 2]; //返回中位数数组的中位数
}
inline void Swap(int a, int b) {
int temp = num[a];
num[a] = num[b];
num[b] = temp;
}
void partation(int l,int r,int div) { //以devide为基准点的快排
int ll = l-1, rr = r , cur = l;
while (cur < rr) {
if (num[cur] < div) {
ll++;
Swap(ll, cur);
cur++;
}
else if (num[cur]==div) {
cur++;
}
else {
rr--;
Swap(rr, cur);
}
}
cnt[0] = ll + 1;
cnt[1] = rr - 1; //表示中间区域的坐标
}
int bfprt(int l,int r,int k) { //依然含前不含后,这里的k从0开始
if (l == r-1) { //只有一个数就返回
return num[l];
}
int divide = part_sort(l, r);
partation(l, r, divide);
if (k >= cnt[0] && k <= cnt[1]) return num[k];
else if (k < cnt[0]) return bfprt(l, cnt[0] , k);
else return bfprt(cnt[1] + 1, r, k);
}
int main() {
int n, k;
cin >> n;
for (int i = 0; i < n; i++) num[i] = read();
cin >> k;
cout << bfprt(0, n, k - 1) << endl; // 这里的k从1开始
return 0;
}