线性时间选择

问题:

给定线性序集中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种,是不是我写的不好
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值