O(NlogN)复杂度选取出现次数超过一半的元素(递归版本)

来源:互联网 发布:淘宝联盟自己买自己的 编辑:程序博客网 时间:2024/04/30 04:10

这个问题以前在书上碰到过,书上采取的办法是从相邻的两个数开始挑选,保证该元素一定会进入下一次的候选集合,且仍然满足出现次数超过一半,具体实现采用的C++.这一次我重新想了一个办法,利用分治算法递归的去实现:

主要思路是获取两个子区间内的主元(出现次数超过一半的元素)。如果两个主元一致则就是该区间的主元。否则出现次数较大的那一个主元有可能是主元。那么就要遍历另一半区间计算可能的主元出现的总次数。最后判断是否是真正主元。

若两个主元不同且出现次数一样多,就要判断是不是区间长度是不是奇数,如果是主元只可能是在稍多一个数的区间里面,则也需要遍历来判断.

O(k)=2O(k2)+k2

容易计算的O(k)是klogk。

下面进行证明,两个候选主元一致则无需判断,主要说明两个主元不一致的情况。假设左右子区间主元分别是A,B,在左右子区间出现的次数分别是A0,B1A1,B0,总的出现次数是A0+A1,B0+B1,其中A0B0是已经获取的数据,A1,B1是在另外一个区间出现的次数(未知).

[1]假设A0>B0B0(A0),,B0,那么有:
A0+B1>B0+B1>n2=>A0+B1>=n2+2
A0+B1A,B,n2+2

[2]假设A0=B0,采取同[1]的思路,可以得到
A0+B1>=n2+1B
,
A1+B0>=n2+1(A)
,当n=2k,左右子区间的长度均为k,所以不可能达到k+1,

n=2k+1.k,k+1,所以较长的那一个区间可能会含有真正主元的另一部分,那么显然候选主元来自较短的那一个区间!因此需要对较长区间进行遍历判断候选主元是否是真正主元。

以下代码可以获取到主元是否出现和出现的次数。相比较以前的算法可能速度上慢一些但是能获取更多的信息。

def Find_main_elem(array,beg,end):#recursion version    def find_most(beg,end,obj):        num = 0        for each in array[beg:end+1]:            if each == obj:num+=1        return num    length = end - beg + 1    if (beg == end):return array[beg],1    if not length%2:mid = (beg+end)//2+1#偶数    else:mid = (beg+end)//2    first_elem,num1  = Find_main_elem(array,beg,mid-1)    second_elem,num2 = Find_main_elem(array,mid,end)    if(num1 or num2):        if first_elem == second_elem:return first_elem,num1+num2        elif num2 > num1 : obj = second_elem;num = num2 + find_most(beg,mid-1,obj)        elif num2 < num1 : obj = first_elem;num = num1 + find_most(mid,end,obj)        elif length%2    : obj = first_elem;num = num1 + find_most(mid,end,obj) # num1 = num2,奇数,那么只可能是first        else:            return (None,0)        return (obj,num) if  (num > length/2) else (None,0)    return (None,0)def Find(array):    x,num = Find_main_elem(array,0,len(array)-1)    if num!=0:return(x,num)    else:return 'no'

进行检验:如果不需要考虑内存的情况,那么最快的办法应该是下面的思路:

def Fast_Find(array):    cache = {}    max,num = None,0    for each in array:        if each in cache:cache[each]+=1        else:cache[each] = 1        if cache[each] > num:num = cache[each];max = each    if num > len(array)/2:return(max,num)    return 'no'

利用上面的函数我们可以检验第一种算法的正确性:

import randomdef Test():    for case in range(150):        length = 50        rnd_array = [random.randint(0,1) for x in range(length)]        if Fast_Find(rnd_array)!=Find(rnd_array):            print(rnd_array);breakTest()

PS:我在进行检验的时候发现了一个小错误,这个错误花费了我半个小时才搞明白原因:在python里面0==False的结果是True,所以如果主元是0可能就会进入某一个处理没有主元的分支造成逻辑错误。因此我将没有主元的情况返回None而不是False。

0 0