1. 问题描述
假设我们有一个无序的数组,我们想知道当前数组中是否存在出现次数大于等于数组元素的一半的元素,如果存在这样的元素,该元素值是多少,如何以高效的算法来完成此任务?相关的题可以如LeetCode
中的169
题和269
题。
2. 简单解法
简单的解法就是先对数组进行排序,如果存在这样的一个数,那么中间值一定是该数,为了确认该数出现次数大于等于数组元素的一半,需要再次遍历整个数组并保存该数出现的次数。有于排序,该解法的时间复杂度为O(nlgn)
,是不是还有更好的解法?
3. Boyer-Moore Algorithm
Boyer-Moore Algorithm
算法为多数表决算法,可参考论文http://www.cs.rug.nl/~wim/pub/whh348.pdf,该算法使用O(1)
的时间复杂度和O(n)
的空间复杂度来解决该问题,它使用两次遍历数组。
第一次遍历找出一个候选元素,这里变种就是有多个候选元素,第二遍只计算候选元素在数组中出现的次数以确认该元素(或所有候选元素)是否为最后答案。
在第一次遍历中,我们需要两个变量“
1) 一个candidate
变量,初始化为任意值;
2)一个count
变量,初始化为0
;
遍历数组的每一个元素,首先判断count
的值,如果count
值为0
,就设置candidate
的值为当前元素,接下来比较当前元素的值是否和candidate
相等,如果相等count
自增1
,否则count
减小1
。
算法描述
1 candidate = 0
2 count = 0
3 for i = 0 to A.length
4 if count = 0
5 candidate = A[i]
6 if canjdidate = A[i]
7 count = count + 1
8 else count = count - 1
遍历完数组中的元素,如果主元素存在,candidate
可能就是主元,第二次循环用来验证candidate
是否是主元素。
4. 详解
为了了解该算法是如何工作的,我们举例说明:
[5, 5, 0, 0, 0, 5, 0, 0, 5]
在处理第一个元素时,count
为0
,将5
分配给候选对象,count
为1
,遇到下一个元素又为5
,则将count
加1,当遇到第三个0
时,此时的count
已经被减为0
,将元素0
分配给候选对象,count
为1
,直到遍历完数组中所有的元素,候选对象有可能为问题的解,于是再次遍历数组验证候选对象是否为解。
5. 分布式Boyer-Moore Algorithm
该算法可以用于并行查找,基本思想是将原数组分成多段数组,每段计算出(candidate, count)
二元组,然后合并得到最终结果。
如将数组[1,1,0,1,1,0,1,0,0]
分成两个子数组[1,1,0,1,1]
和[0,1,0,0]
后分别将计算出二元组(1, 3)
和(0, 2)
,合并结果得原数组最多元素为1
。可参考http://www.crm.umontreal.ca/pub/Rapports/3300-3399/3302.pdf
5. 参考
[1] https://gregable.com/2013/10/majority-vote-algorithm-find-majority.html
[2] http://www.cs.rug.nl/~wim/pub/whh348.pdf
[3] http://www.crm.umontreal.ca/pub/Rapports/3300-3399/3302.pdf