问题:在一个数组中同时寻找最大值和第二大值,这里假设数组的元素个数n大于2
方法一:遍历数组,设置max和secondMax标志,如果有大于max的就更新max,如果有小于max但是大于secondMax的就更新secondMax。
比较次数:在a[0]和a[1]中找出临时的max和secondMax需要一次比较。在剩下的n-2个数中最坏时需要同max和secondMax分别比较,总共比较2(n-2),所以总的比较次数为1+2(n-2)。代码如下:
void traverse_find(int a[],int len,int *max,int *secondMax){
int maxt,secondMaxt;
int i;
if(a[0]<a[1]){ //选出临时的最大和第二大值,需要一次比较
maxt = a[1];
secondMaxt = a[0];
}else{
maxt = a[0];
secondMaxt = a[1];
}
for(i=2;i<len;i++){
if(a[i]>maxt){<span style="white-space:pre"> </span>
secondMaxt = maxt;
maxt = a[i];
}else if(a[i]>secondMaxt){ //这种情况是需要两次比较
secondMaxt = a[i];
}
}
*max = maxt;
*secondMax = secondMaxt;
}
方法二:使用锦标赛算法,设置一个额外的大小为2n的数组(在优化的算法中额外数组的大小为n就可以)b,首先将原数组的n个数复制到额外数组的后n个位置,将额外数组b理解为一个类似于最大堆的结构,然后从倒数第二位置开始,以此选择两个数的较大者放到父亲节点中,依次进行,直到到根节点(b[1]),那么b[1]一定是这n个数中的最大值,第二大值一定同最大值比较过且输给了最大值,因此找第二大值时只需要从直接输给最大值的元素中去找,直接输给最大值的元素的个数有“lgn的上界”个;找最大值需要n-1次比较,找第二大值需要“lgn的上界”-1次比较,所以一共需要n+"lgn的上界"-2次比较,实用敌手策略可以证明在最坏情况下这是寻找第二大值的最少的比较次数。
示意图如下,其中红色数字表示最大值的上升过程,黄色节点表示直接输给最大值的元素,找第二大值就从这些黄色节点中寻找。
1):使用额外空间为2n的锦标赛算法,这个算法直接申请空间为2n的数组,并直接将已有数组复制到额外数组的后n个位置中:
void tournamet(int a[],int len,int *max,int *secondMax){
int aLen = 2*len;
int *ta = (int*)malloc(sizeof(int)*aLen);
int i;
int secondLarge = -1;
for(i=len;i<aLen;i++){
ta[i] = a[i-len];
}
for(i=aLen-2;i>=2;i-=2){
ta[i/2] = max(ta[i],ta[i+1]);
}
*max = ta[1]; //ta[1]即是保存的最大值
//下面寻找第二大值,在直接输给最大值的的数中找
for(i=1;2*i<aLen;){
if(ta[2*i]==ta[i]){
if(secondLarge<ta[2*i+1])
secondLarge = ta[2*i+1];
i = 2*i;
}else{
if(secondLarge<ta[2*i])
secondLarge = ta[2*i];
i = 2*i+1;
}
}
*secondMax = secondLarge;
}
2):使用额外空间为n的锦标赛算法,这个算法直接使用原来的数组作为堆的叶子节点,在寻找的过程中如果超过额外数组的大小就从原始数组中寻找,最大下标不超过2n。算法如下,注意存在一个节点的左孩子和右孩子分别在两个数组中情况:
void tournamet_modify(int a[],int len,int *max,int *secondMax){
int aLen = 2*len;
int *ta = (int*)malloc(sizeof(int)*len);
int i;
int secondLarge = -1;
for(i=aLen-2;i>=2;i-=2){
if(i>=len){
ta[i/2] = max(a[i-len],a[i+1-len]);
}else if(i==len-1){
ta[i/2] = max(a[0],ta[i]);
}else{
ta[i/2] = max(ta[i],ta[i+1]);
}
}
*max = ta[1]; //ta[1]即是保存的最大值
//下面寻找第二大值,在直接输给最大值的的数中找
for(i=1;2*i<aLen;){
if(2*i<len-1){
if(ta[2*i]==ta[i]){
if(secondLarge<ta[2*i+1])
secondLarge = ta[2*i+1];
i = 2*i;
}else{
if(secondLarge<ta[2*i])
secondLarge = ta[2*i];
i = 2*i+1;
}
}else if(2*i==len-1){
if(ta[2*i]==ta[i]){
if(secondLarge<a[0])
secondLarge = a[0];
i = 2*i;
}else{
if(secondLarge<ta[2*i])
secondLarge = ta[2*i];
i = 2*i+1;
}
}else{
if(a[2*i-len]==ta[i]){
if(secondLarge<a[2*i-len+1])
secondLarge = a[2*i-len+1];
i = 2*i;
}else{
if(secondLarge<a[2*i-len])
secondLarge = a[2*i-len];
i = 2*i+1;
}
}
}
*secondMax = secondLarge;
}
上面三个方法可以使用下面main函数调用:
void main(){
int a[] = {100,23,43,6,123,8,8,99,20};
int max,secondMax;
int maxt,secondMaxt;
tournamet_modify(a,9,&max,&secondMax);
traverse_find(a,9,&maxt,&secondMaxt);
printf("max:%d,second max:%d\n",max,secondMax);
printf("max:%d,second max:%d\n",maxt,secondMaxt);
printf("\n");
}