主元素就是指数组中,出现次数大于数组长度的一半的数。
在习题中,给出的解答如下:
int majority(int A[],int n)
{
int i, c, count = 1;
c = A[0];
for(i=1;i<n;i++)
{
if (A[i] == c)
count++;
else
{
if (count > 0)
count--;
else
{
c = A[i];
count = 1;
}
}
}
if(count>0)
{
for(i=count=0;i<n;i++)
{
if (A[i] == c)
count++;
}
}
if (count > n / 2) return c;
return -1;
}
具体思路是:
1.筛选可能的主元素,记录为a(只筛选一次)
2.判断该原元素是否为主元素。(遍历一次,记录次数,看是否大于长度的一半)
这里最大的问题在于:
为什么只需要筛选一次,就可以得到候选主元素?
下面将从数学的角度去说明这一点。
前提:
1.假设主元素出现了x次,那这个数组的长度最长是2*x-1.也就是说,主元素出现次数至少大于数组长度的一半。
推论:
一、记录第一个元素,标记次数count=1.
下一次遇到该元素则+1,没有遇到则-1.
如果这个元素是主元素,则最终count必然大于1.
下面我们从最简单的例子来入手:
1.假设这个数组为A={1,1}则显然按照上面的计算,count=2>0,为主元素。
2.如果A={1,2}呢?count=1-1=0,不大于0,所以不是主元素
那我们递推一下。
1.如果A={1,1,2},则count=1+1-1=1>0,成立
2.如果A={1,2,1},则对最初的1来讲,刚开始就遇到了2,于是count(1)=1-1=0。
接下来就是最重要的部分了!!!
标准答案在处理的时候,跳过了数组中count=0的第一位(1),继续处理之后的第二位(2),并且把count(2)=1
继续按照之前的算法,数组的第二位(2)和第三位(1)进行比较,发现不同,因此count(2)=1-1=0
接下来继续循环,来判断第三位(1),并将count(3)置为1.
此时循环已经结束了。
最后留下的count=1的数据,正是1.
因此只需要判断1是不是主元素即可。
最后检验1的确是主元素。
那么这是不是巧合呢?为什么可以不管前面的那些count=0的数据呢?
结论1
凡是出现了count=0,则表示该元素从计数起到结束,所经历的数组元素中,有一半是该元素,有一半不是。
例如11122211222
对于开始的1,结束的时候是在111222的位置。这期间1的出现次数,和“非1”的出现次数一样。
结论2
如果经历了一段数组,其中有一半是元素a,另一半不是a,则可以去掉这经历的一半,因为这里面的即使有主元素,根据假设1的对称性,也可以去掉这一段数组,从剩余数组中寻找候选主元素。
这样看来,这个算法是正确的。
有一种极端情况,例如121212
最终前五个数的count均为0,只有最后一个2,因为没有后续,所以count=1,此时会只需要判断一下,此时2就是候选主元素。只需要遍历一次数组,看看2是否为主元素即可。