c++实现 数组中的选择问题——最大,最大最小,第二大,第k小

本文详细介绍了选择问题的不同类型及其解决算法,包括寻找最大值、最大最小值、次大值及第k小值的方法。探讨了顺序算法、分组方法、分治法及锦标赛算法等,并给出了具体的实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天介绍一下选择问题,包括选最大,选最大最小,第二大,和一般选择问题即选第k小的数。

概括:选最大使用顺序算法,选最大最小可以使用顺序算法、分组方法,分治法,第二大可以使用顺序算法和锦标赛算法,选第k小的数,这里介绍一个改进的分治法算法(一般的分治法是随机选择进行比较的数,改进算法是对进行比较的数进行选择最优的)下面依次介绍。

选最大

直接使用顺序比较,定义max变量,初始值为第一个数,依次向后比较,只存储最大的那个数。比较到数组结束,即可得到结果。

int chooseMax(vector<int> a){
	int max = a[0];
	for(int i=1;i<a.size();i++){
		if(max<a[i]) max = a[i];
	}
	return max;
}

比较次数为:n-1

选最大最小

1、顺序比较:先选出最大,在从剩下中选出最小。

void chooseMaxAndMin(vector<int> a){
	int max = a[0];
	int locate =0;
	int min = a[0];
	
	//顺序查找
	for(int i=0;i<a.size();i++){
		if(max<a[i]){
			max = a[i];
			locate = i;
		}
	} 
	int i = 0;
	while(i<a.size()){
		if(i!=locate && min>a[i]) min = a[i];
		i++;
	}
	cout<<"max = "<<max<<endl;
	cout<<"min = "<<min<<endl;	
} 

W(n) = n-1+n-2 = 2n-3

2、分组。

void findMaxMin(vector<int> a){
	int size ;
	vector<int> max;
	vector<int> min;
	if(a.size()%2==1){
		size = a.size()-1;
		max.push_back(a[size]);
		min.push_back(a[size]);
	}else{
		size = a.size();
	}
	for(int i=0;i<size;i+=2){
		if(a[i]<a[i+1]){
			max.push_back(a[i+1]);
			min.push_back(a[i]);
		}else{
			max.push_back(a[i]);
			min.push_back(a[i+1]);
		}
	} 
	int rmax = max[0];
	int rmin = min[0];
	for(int i=1;i<max.size();i++){
		if(rmax < max[i]) rmax = max[i];
		if(rmin > min[i]) rmin = min[i];
	}
	cout<<"max = "<<rmax<<endl;
	cout<<"min = "<<rmin<<endl;
} 

比较次数:\left \lceil 3n/2 \right \rceil-2

3、分治

     1)、将数组L从中间火焚为两个子数组L1和L2

     2)、递归的在L1中求最大max1和最小min1

     3)、递归的在L2中求最大max2和最小min2

     4)、max   <-  max{max1,max2}     min <-  min{min1,min2}

vector<int> findMaxMin2(vector<int> a,int begin,int end){
	int max,min;
	vector<int> result; 
	if(begin==end){   //只有一个数 
		min = a[begin];
		max = a[begin];
		result.push_back(max);
		result.push_back(min);
		return result;
	}else if((end-begin)==1){   //剩两个数 
		if(a[begin]>a[end]){
			max = a[begin];
			min = a[end];
		}else{
			min = a[begin];
			max = a[end];
		}
		result.push_back(max);
		result.push_back(min);
		return result;
	}else{
		int s = (end-begin)/2 + begin;    //这里一开始没有加begin出错,要注意 
		vector<int> result1 = findMaxMin2(a,begin,s);
		vector<int> result2 = findMaxMin2(a,s+1,end);
		if(result1[0]>result2[0]){    //max 
			result.push_back(result1[0]);
		}else{
			result.push_back(result2[0]);
		}
		if(result1[1]<result2[1]){    //min 
			result.push_back(result1[1]);
		}else{
			result.push_back(result2[1]);
		}
		return result;
	}
}

\left\{\begin{matrix} W(n) = 2W(n/2)+2\\ W(2) = 1 \end{matrix}\right.\Rightarrow W(n) = \frac{3}{2}n-2

选第二大的数

1、顺序

顺序比较,先选出最大的,在从剩下的n-1个数中选出最大,即第二大的数。

int findSecond(vector<int> a){
	int size = a.size();
	int max = a[0];
	int locate =0;
	for(int i=1;i<size;i++){
		if(max<a[i]) {
			max = a[i];
			locate = i;
		}
	}
	int i=0;
	int second = a[0];
	while(i<size){
		if(locate!=i&&second<a[i]) second = a[i];
		i++;
	}
	cout<<"second  = "<<second<<endl;
	return second;
} 

W(n) = n-1 + n-2 = 2n-3

2、锦标赛算法

首先挑选第二大的数的条件为:第二大数肯定是在与最大的数比较中被淘汰的。我们可以在确定最大数的过程中,确定被这个数淘汰掉的数有哪些,在从这些数中找出最大的数,就找到了第二大的数。这就是锦标赛算法的基本思想,他是基于空间换时间的思想进行设计的。

锦标赛算法:

1)两两分组进行比较,较大的那个进入下一轮比较,小的数被淘汰,并将其记录在淘汰他的元素的链表上,知道剩下一个元素max为止

2)检查max的链表,从中找到最大的数,得到second

int findSecond2(vector<int> a){
	unordered_map<int,vector<int>> map;
	 int size = a.size();
	 while(size>1){
	 	vector<int>::iterator it = a.begin();
	 	vector<int> temp;
	 	for(int i=0;i<size/2;i++){
//	 		int j = i*2;
	 		if(a[i]<a[i+1]){
	 			map[a[i+1]].push_back(a[i]);
				it = a.erase(it);
				++it;	 
	 		}else{
	 			map[a[i]].push_back(a[i+1]);
	 			++it;
	 			it = a.erase(it);
	 			
			 }
		 }
		 size = a.size();
	}
	int max = a[0];
	vector<int> smax = map[max];
	cout<<"smax.size()"<<smax.size()<<endl;
	int second  = smax[0];
	for(int i=1;i<smax.size()-1;i++){
		if(second <smax[i]){
			second = smax[i];
		}	
	}
	return second;
}

时间复杂度:包括   找最大数的过程比较次数为n-1 和第二阶段找第二大数的过程,元素总共\left \lceil logn \right \rceil比较次数为:\left \lceil logn \right \rceil-1W(n) = n-1 +\left \lceil logn \right \rceil-1 = n+\left \lceil logn \right \rceil-2

一般选择问题  求第k小的数

这里主要介绍分治的思想。

首先选取一个比较标准数m*,将整个数组S划分为S1和S2,其中S1是比m*小的所有数,S2是比m*大的所有数

判断如果k<=|S1| 则在S1中找  如果K=|S1|+1,则m*即为所求  若K>|s1|+1 则在S2中找

这个是普通的分治算法,一般情况下可以选取当前数组的第一个数作为标准数进行比较。我们可以看出算法的效率取决于子问题的规模,通过m*可以控制子问题规模。有一种更为优化的算法,就是将数组的中位数当作标准数。

选择m*:将S分为5个一组,共\left \lfloor n/5 \right \rfloor组。每组进行排序,所有的中位数放到一个M数组中。将M数组的中位数作为m*。

int select(vector<int> a,int begin,int end,int k){
	//将a分为5个一组,从大大小排列, 
	vector<int> middle ;
	int size = end - begin + 1;
	vector<int> v1;
	vector<int>::iterator it = a.begin();
	int partition_num = 0;  //分组个数 
    if(size<5){  
    		sort(it,it+size);
            return a[begin+k-1];  
        }  
	
	while(size>=5){
		partition_num++;
		sort(it,it+5);
		middle.push_back(*(it+2));
	
		it = it + 5;
		size = size - 5;	
	} 

 	int m;
	if(middle.size()==1) m=middle[0];
	else
	m = select(middle,0,middle.size()-1,middle.size()/2);
	vector<int> s1;   //小于m的 
	vector<int> s2;  //大于m的 
	int c = 0;
	if(size!=0){
		while(c!=size){
			int location = partition_num*5+c;
			if(a[location]>m) s2.push_back(a[location]);
			else s1.push_back(a[location]);
			c++;
		}
	}
	size = a.size() - c;
	it = a.begin()+2;
	while(size>=5){
		if(*it>m){
			s2.push_back(*it);
			s2.push_back(*(it+1));
			s2.push_back(*(it+2));
			if(*(it-1)>m) s2.push_back(*(it-1));
			else s1.push_back(*(it-1));
			if(*(it-2)>m) s2.push_back(*(it-2)); 
			else s1.push_back(*(it-2));
		}
		else{
			if(*it<m)s1.push_back(*it);
			s1.push_back(*(it-1));
			s1.push_back(*(it-2));
			if(*(it+1)>m) s2.push_back(*(it+1));
			else s1.push_back(*(it+1));
			if(*(it+2)>m) s2.push_back(*(it+2)); 
			else s1.push_back(*(it+2));
		}
		it = it+5;
		size -= 5;
	}
	int s1_size = s1.size();
	int s2_size = s2.size();
	if(s1_size+1==k){
		return m;
	} else if(s1_size+1>k){   //搜索左边的数组 
		return select(s1,0,s1.size()-1,k);
	}else{
		return select(s2,0,s2.size()-1,k-s1_size-1);
	}
	
	
} 

时间复杂度:

W(n) = W(n/5)+W(7n/10) +cn \Rightarrow W(n) \leq cn(0.9+0.9^{2}+0.9^{3}+...) = O(n)

PS:如果大家觉得代码有哪里有问题的话可以直接与我联系,轻喷。。。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值