线性时间选择

1.问题描述
输入:给定n个元素的线性序集和一个整数k
输出:第k小的元素
2.实验目的
利用线性时间选择算法在一个数组中找出第k小的数,理解递归与分治思想在线性时间选择算法中的运用。
3.实验原理
如果能在线性时间内找到一个划分基准,使得按这个基准所划分出的两个子数组的长度都至少为原数组的n倍(0<n<1),那么就可以在最坏情况下用O(n)的时间完成选择任务。
按以下步骤可以找到满足要求的划分基准:
(1)将n个元素划分成⌈n/5⌉个组,每组5个元素,只可能有一组不是5个元素,用任意一种方法将每个数组排序,并取出各自的中位数,共⌈n/5⌉个。
(2)递归调用select算法来找出这⌈n/5⌉个元素的中位数,如果⌈n/5⌉是偶数,就找两个中位数中较大的,以这个中位数做为划分基准。
4.实验设计
public int select(Integer[] arr,int p,int r,int k){
//如果r-p<75,直接对数组进行排序,然后找出第k小的数
if(r-p<75){
Arrays.sort(arr);
return arr[p+k-1];
}
//将每组中的中位数调换到整个数组的左边去
for(int i=0;i<=(r-p-4)/5;i++){
//5个数据分为一组,对每组数据进行排序
Arrays.sort(arr, p+5i, p+5i+4);
//将每一组数据的中位数都调换到整个数组的左边
int temp = arr[p+2+i5];
arr[p+2+i
5]=arr[p+i];
arr[p+i] = temp;
}
//递归调用select()方法找出中位数中的中位数
int x = select( arr,p,(r-p-4)/5,(r-p-4)/10);
//根据x对数组进行划分,得到位于中间的元素的下标i
int i = partition(arr, p, r,x);
int j=i-p+1;
if(k<=j){
//如果k<j,就在0到i的范围内找第k小的数
return select(arr, p, i, k);
}else{
//如果k>j,就在i+1到r的范围内找第k-j小的数
return select(arr, i+1, r, k-j);
}
}
5.实验结果与分析
当n<75时算法select的时间复杂度为一个常数。找到中位数的中位数x后,算法select以x为基准调用partition对数组进行划分,这需要O(n)时间。算法select的for循环体共执行n/5次,每一次需要O(1)时间,因此共需要O(n)时间。
找中位数的中位数x至多用了T(n/5)的时间,按照算法所选的基准进行划分所得到的两个子数组分别至多有3n/4个元素,所以无论哪个子数组调用select都至多用T(3n/4)的时间T(n)=C2n+T(n/5)+T(3n/4)解得T(n)=O(n)。
在本次实验设计的select算法中对问题进行了简化,要求输入的元素不重复,所以一旦重复数据太多,算法就需要改进。
6.结论
线性时间选择算法实际和快速排序算法的思想差别不大,都是对数组进行递归划分,但该算法最精妙的地方在于找中位数的中位数做为划分基准,这大大降低了算法的时间复杂度。

import java.util.Arrays;
/*
 * 线性时间选择
 * 
 */
public class Select {
	public int getNumber(Integer[] arr,int p,int r,int k){
		//如果r-p<75,直接对数组进行排序,然后找出第k小的数
		if(r-p<75){
			Arrays.sort(arr);
			return arr[p+k-1];
		}
		//将每组中的中位数调换到整个数组的左边去
		for(int i=0;i<=(r-p-4)/5;i++){
			//5个数据分为一组,对每组数据进行排序
			Arrays.sort(arr, p+5*i, p+5*i+4);
			//将每一组数据的中位数都调换到整个数组的左边
			int temp = arr[p+2+i*5];
			arr[p+2+i*5]=arr[p+i];
			arr[p+i] = temp;			
		}
		//递归调用getNumber()方法找出中位数中的中位数
		int x = getNumber( arr,p,(r-p-4)/5,(r-p-4)/10);
		//根据x对数组进行划分,得到位于中间的元素的下标i
		int i = partition(arr, p, r,x);	
		int j=i-p+1;
		if(k<=j){
			//如果k<j,就在0到i的范围内找第k小的数
			return getNumber(arr, p, i, k);
		}else{
			//如果k>j,就在i+1到r的范围内找第k-j小的数
			return getNumber(arr, i+1, r, k-j);
		}
	}
	
	//根据x进行划分
	private int partition(Integer[] arr, int left, int right,int x) {
		int i = left;
		int j = right+1;
		//将小于x的元素交换到左边
		//将大于x的元素交换到右边
		while(true){
			while(arr[++i]<x && i<right);
			while(arr[--j]>x);
			if(i>=j) break;
			int temp;
			temp=arr[i];
			arr[i]=arr[j];
			arr[j]=temp;
		}
		arr[left] = arr[j];
		arr[j] = x;
		return j;
	}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值