问题
找出数组中的主元
设计并实现使用分治求主元的算法
主元(majority element)指:大小为N数组中出现次数超过N/2的元素
候选元(candidate ele):参见书
注意:不确定数组中是否存在主元!
算法设计
如何优雅的解决这个问题困扰了我一段时间:
简单的方法,自然是遍历一遍数组,时间复杂度O(N)
int find(int a_copy[],int len){ int cnt=0; int now; for(size_t i=0;i<len;i++){ if(cnt==0) now=a_copy[i]; if(now==a_copy[i]) cnt++; else cnt--; }
if(Trivialjudge(now)==true) return now; else return _INI_MAX_;//(0x7fffffff) } |
直观上理解为,不同的元素相互抵消,最多的元素将留下 |
但是如果按书上两个两个元素筛选候选元的思想,就比较麻烦,也可以理解为上面那个方法,因为不同的两个元素被抵消了,剩下相同的元素(不能边抵消边融合元素,因为融合后与融合前权重不一样)
首先确定此做法的原理:
如果数组中有主元
在数组中,主元的权重>N/2;其他元素的权重最大也小于N/2;
在"抵消"之后,主元被抵消,则其他元素的最大权重-1,主元权重-1;
在"合并"之后,主元权重不变,其他也不变;
所以在"抵消" 后,权重最大的仍然是主元;
遇到奇数长度的数组怎么办呢?如下图所示,2表示已经合并了,1表示是奇数数组的最后一个元素
|这种方法是我无意中想到的,用一个变量记录末尾的未合并元素数量
|
如果数组中没有主元
不论最后找出来的是什么,只要O(N)时间复杂度的检验,就可以判断找出来的是否是主元
过程分析
先将能合并的合并(同样权重),不能合并的留在后面,每次合并都拷贝在数组最后面;
合并完后,选择第一个元素即是可能的候选元;如果第一个元素都没有,就视为没有候选元
代码
这只是表达思想,实际运用中不用分治;
int findMaj(int a[],int len){ int copy[len+1]; //防止越界 memcpy(copy,a,len*sizeof(int)); if(findCan(copy,len)){ int can=*copy; for(int i=0,cnt=0;i<len;i++){ if(a[i]==can) cnt++; if(cnt>len/2) return can; } } return 0x7fffffff; } |
int findCan(int a[],int len){ int rea=0; while(len>=2){ int len2=0; for(int i=0;i<len-1;i+=2) if(a[i]==a[i+1]) a[len2++]=a[i];
if(len&1){ rea++; memcpy(a+len2,a+len-1,sizeof(int)*rea); }else{ memcpy(a+len2,a+len,sizeof(int)*rea); } len=len2; } if(len==0 && rea==0) return 0; else return rea; } |
时间复杂度
查找候选元的算法运行时间设为T(N),有递推式T(N)=T(N/2)+O(N),解得T(N)=O(N),查找主元的算法P(N)=O(N)