问题:
给定线性序集中n个元素和一个整数k,1≤k≤n,要求找出这n个元素中第k小的元素
参考
法1:
randomizedSelect
思想:改编随机快速排序,不用把整个数组全部排序,而是选择的排序(更快)
时间复杂度:
(1)在最坏情况下,算法randomizedSelect需要O(n^2)计算时间
例如要找最小的元素,但是每次进行Partition函数划分时得到的位置总是很大(靠近n)(即总是在最大元素出划分)
(2)但可以证明,算法randomizedSelect可以在O(n)平均时间内找出n个输入元素中的第k小元素。
int randomSelect(int left, int right, int k){
if(left == right) return a[left];
int i = randomParse(left, right), j = i-left+1;
if(k <= j) return randomSelect(left, i, k);
return randomSelect(i+1, right, k-j);
}
法2:
从上面复杂度分析可以看出,上面的算法对划分的依赖很高,当划分的好时效率很高,划分不好时较慢,从这里入手改进。
如果能在线性时间内找到一个划分基准,使得按这个基准所划分出的2个子数组的长度都至少为原数组长度的ε倍(0<ε<1是某个正常数),那么就可以在最坏情况下用O(n)时间完成选择任务。
例如,若ε=9/10,算法递归调用所产生的子数组的长度至少缩短1/10。所以,在最坏情况下,算法所需的计算时间T(n)满足递归式T(n)≤T(9n/10)+O(n) 。由此可得T(n)=O(n)。
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
typedef long long LL;
const int maxn = 100+5;
int a[200000];
//1. 仿照快速排序, 随机化划分, 求第k小. 最差情况下O(n^2), 平均性能不错
int parse(int left, int right){
int i = left, j = right;
int x = a[left];
while(true){
while(a[j] >= x&&i < j) --j;
while(a[i] <= x&&i < j) ++i;
if(i >= j) break;
swap(a[i], a[j]);
}
a[left] = a[i];
a[i] = x;
return i;
}
int randomParse(int left, int right){
srand(time(NULL));
int p = rand()%(right-left) + left;
swap(a[p], a[left]);
return parse(left, right);
}
int randomSelect(int left, int right, int k){
if(left == right) return a[left];
int i = randomParse(left, right), j = i-left+1;
if(k <= j) return randomSelect(left, i, k);
return randomSelect(i+1, right, k-j);
}
//2.线性时间选择
void bubbleSort(int left, int right){
for(int i = left; i < right; ++i){
for(int j = i+1; j <= right; ++j){
if(a[j] < a[i]) swap(a[j], a[i]);
}
}
}
int parse(int left, int right, int v){
for(int i = left; i <= right; ++i) if(a[i] == v){
swap(a[i], a[left]);
break;
}
// if(v == a[left]) printf("equal v = %d,%d\n",v,a[left]);
// else printf("no v = %d != %d\n", v, a[left]);
int x = a[left]; //基准数
int i = left, j = right;
while(true){
while(a[j] >= x&&i < j) --j;
while(a[i] <= x&&i < j) ++i;
if(i >= j) break;
swap(a[i], a[j]);
}
a[left] = a[i];
a[i] = x;
return i;
}
int select(int left, int right, int k){
int n = right - left + 1;
if(n < 75){
bubbleSort(left, right); //用某个简单排序算法对数组a[p:r]排序
return a[left+k-1];
}
for(int i = 0; i <= (n-5)/5; ++i){//将a[p+5*i]至a[p+5*i+4]的第3小元素(中位数)与a[p+i]交换位置;
int s = left+5*i, t = s+4;
for(int j = 0; j < 3; ++j){
int minx = a[s+j], minp = s+j;
for(int k = s+j+1; k <= t; ++k) if(a[k] < minx) {
minx = a[k];
minp = k;
}
swap(a[minp], a[s+j]);
}
swap(a[left+i], a[s+2]);//将中位数元素换至前面
}
int x = select(left, left + (n-5)/5, (n-5)/10);//找中位数的中位数
//printf("x = %d, l = %d, r = %d, %d\n",x,left,left+(n-5)/5,a[left]);
int pos = parse(left, right, x);//pos为找到区间[left,right]中x应该在的位置,c为[left,right]区间的元素个数
int c = pos-left+1;
if(k <= c)
return select(left, pos, k);
else
return select(pos+1, right, k-c);
}
int main()
{
int num = 100000, k;
srand(time(NULL));
for(int i = 0; i < 100000; ++i){
a[i] = rand()%200000;
}
int start, finish;
while(scanf("%d",&k) == 1){//求第k小
start = clock();
int x = randomSelect(0, num-1, k);
finish = clock();
printf("first: %d, Total_time = %d\n", x,finish-start);
start = clock();
x = select(0, num-1, k);
finish = clock();
printf("second: %d, Total_time = %d\n", x,finish-start);
//for(int i = 0; i < num; ++i) printf("%d ", a[i]);
}
return 0;
}
然而,经过测试,第一个算法的效率要明显好于第2种,是不是我写的不好