算法设计与分析——递归与分治策略——线性时间选择

顾名思义:这篇文章讲解的就是如果用线性时间算法来作出元素选择问题。
问题描述:给定线性序集中n个元素和一个整数k,1<=k<=n.要求找出这n个元素中第k小的元素,即如果将这个n个元素依其线性序排列时,排在第k个位置的元素就是要找的元素,当k== 1时,要找的就是最小的元素;当k==n,就是最大的元素;当k=(n+1)/2,称为中位数。

问题分析:
在某些特殊的情况下,我们可以实现线性时间选择,对于找最大最小的元素O(n)内可以实现;当k<=n/logn,通过堆排序算法可以在O(n+klogn)=O(n)内实现;当k>=n-n/logn时也一样。
下面是给出的一般的选择问题,从渐近阶的意义上看,这个也可以在O(n)时间内完成。
下面的算法实现参考了《计算机算法与分析》和一些博客,是对其的一个整理。

方法一:
算法描述:用一个随机的序列中的数作为枢纽,用快速排序算法,进行一次快排,然后将枢纽值和k值进行比较,以此来确定k值,我并没有做任何的对比所以并不是清楚这种算法的效率有多少,但是搜到的结果表明,这种算法的最坏时间复杂度是O(n^2),相对与另一种是不太理想的。

解法:

1.首先大家都会想到的解法是排序,之后找出第k个元素,但是排序的时间复杂度不符合要求,或者需要额外的空间。

2.利用快排的思想,以枢纽(随机得到)为界,将数组分为2部分,一部分小于等于这个枢纽值,一部分大于这个枢纽值,与快排不同的是,我们只处理一部分,另一部分舍弃。

#include<bits/stdc++.h>
using namespace std;

int partition(vector<int> &vec,int left,int right)
{
	int i=left;
	int j=right;
	int temp=vec[left];
	while(i<j)
	{
		while(vec[j]>=temp&&i<j)//处理i,j的先后顺序不能改变 
		{
			j--;
		}
		
		while(vec[i]<=temp&&i<j)
		{
			i++;
		}
		
		swap(vec[i],vec[j]);
	}
	
	vec[left]=vec[j];
	vec[j]=temp;
	
	return j;
}
int  QuickSort(vector<int> &vec,int left,int right,int k)
{
	if(left==right)
		return vec[left];
	
		int mid=partition(vec,left,right);
		int j=mid-left+1;
		if(k<=j)
			return QuickSort(vec,left,mid,k);
		
		else
			return QuickSort(vec,mid+1,right,k-j);
		
		
		
	
}
void print(int x)
{
	cout<<x<<" ";
}



int main()
{
	vector<int> vec={1,2,3,6,5,4,7,8,9};
	for_each(vec.begin(),vec.end(),print);
	cout<<endl;
	int k;
	cout<<"请输入K(要求找出这n个元素中第k小的元素):" ;
	cin>>k;
	int count=vec.size()-1;
	cout<<QuickSort(vec,0,count,k)<<endl;
	QuickSort(vec,0,count,k);
	
	for(auto a:vec)
	{
		cout<<a<<" ";
	}
	cout<<endl;
	
 } 

在这里插入图片描述
方法二:

这里我们就利用中位数来进行线性时间的选择算法!

中位数就是指将数据按大小顺序排列起来,形成一个数列,居于数列中间位置的那个数据就是中位数。

算法思路
(1)将输入的n个数划分成 ⌈n5⌉⌈n5⌉ 个组,当然最后一组的数目可能是小于5的!
(2)用任意的排序方法对他们进行排序,并取出一共 ⌈n5⌉⌈n5⌉ 个中位数。
(3)找出该 ⌈n5⌉⌈n5⌉ 个中位数中的中位数。(如果 ⌈n5⌉⌈n5⌉ 是偶数则取相对大的那个数)
(4)将全部的数划分为两个部分,小于基准的在左边,大于等于基准的放右边。

我们用小圆点表示元素,得到如下图:

在这里插入图片描述

说明:
图中中间白色圈表示各组数据的中位数,最中间灰色表示中位数的中位数! 箭头是从较小的数指向较大的数!

故我们可以使用该数作为划分的基准(比上一个随机基准的方法会好很多)!
在这里插入图片描述

图中
在这里插入图片描述

当n≥75时,3A1大于等于 14n14n。所以按此基准划分所得的左右2个子数组的长度都至少缩短 1414。

#include<bits/stdc++.h>
using namespace std;

int partition(vector<int> &vec,int left,int right,int k)
{
	int i=left;
	int j=right;
	int temp=vec[k];
	while(i<j)
	{
		while(vec[j]>=temp&&i<j)//处理i,j的先后顺序不能改变 
		{
			j--;
		}
		
		while(vec[i]<=temp&&i<j)
		{
			i++;
		}
		
		swap(vec[i],vec[j]);
	}
	
	vec[k]=vec[j];
	vec[j]=temp;
	
	return j;
}

void print(int x)
{
	cout<<x<<" ";
}

int  Select(vector<int> &vec,int left,int right,int k)
{
	if(right-left<75)
	{
		return vec[left+k-1];
	}

	for(int i=0;i<=(right-left-4)/5;i++)
	{
	}
	
		int x=Select(vec,left,left+(right-left-4)/5,(right-left-4)/10);
	
		int i=partition(vec,left,right,x);
		int j=i-left+1;
		
		if(k<=j)
			return Select(vec,left,i,k);
		else
			return Select(vec,i+1,right,k-j);
		
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
}

int main()
{
	vector<int> vec={1,2,3,6,5,4,7,8,9};
	for_each(vec.begin(),vec.end(),print);
	cout<<endl;
	int k;
	cout<<"请输入K(要求找出这n个元素中第k小的元素):" ;
	cin>>k;
	int count=vec.size()-1;
	cout<<Select(vec,0,count,k)<<endl;
	Select(vec,0,count,k);
	
	for(auto a:vec)
	{
		cout<<a<<" ";
	}
	cout<<endl;
	
 } 

在这里插入图片描述

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值