今天介绍一下选择问题,包括选最大,选最大最小,第二大,和一般选择问题即选第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;
}
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;
}
比较次数:
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;
}
}
选第二大的数
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 和第二阶段找第二大数的过程,元素总共比较次数为:
-1
一般选择问题 求第k小的数
这里主要介绍分治的思想。
首先选取一个比较标准数m*,将整个数组S划分为S1和S2,其中S1是比m*小的所有数,S2是比m*大的所有数
判断如果k<=|S1| 则在S1中找 如果K=|S1|+1,则m*即为所求 若K>|s1|+1 则在S2中找
这个是普通的分治算法,一般情况下可以选取当前数组的第一个数作为标准数进行比较。我们可以看出算法的效率取决于子问题的规模,通过m*可以控制子问题规模。有一种更为优化的算法,就是将数组的中位数当作标准数。
选择m*:将S分为5个一组,共组。每组进行排序,所有的中位数放到一个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);
}
}
时间复杂度:
PS:如果大家觉得代码有哪里有问题的话可以直接与我联系,轻喷。。。