寻找多数元素(算法1)

摘要

这篇文章由投票问题引出,讨论了几种找出占多数元素的算法,并给出最终算法的源代码和递归调试过程,最后提出改进方案。

(一) 问题描述

在选举投票中,常常有这样的规定:如果投某个选项的次数超过全部可投票的一半,那么最终结果就为该选项,否则,此次投票无效。

我们将此问题抽象成数学模型。假设投票结果为长度为n整数序列A,像这样[A[0],A[1],...A[n-1]],如果某个元素的出现次数大于(n/2),就可以断定它是多数元素c;否则,没有多数元素。

(二) 算法分析

有几种算法可以解决这个问题,最蛮力的是,把每一个元素(n个)通过与其他元素比较(n-1次),这样的时间复杂度是Θ(n²),代价太大。如果先排序,再按上述方法计算,则最坏情况下时间复杂度为Θ(n㏒n)。

另一种更聪明方法是看作排序问题,先对A排序,因为多数元素占比超过一半,可以很容易地证明,中间值就是c。这个方法排序时间复杂度Θ(n㏒n),搜索中间值Θ(n),总的为Θ(n㏒n)+ Θ(n)。这个算法隐藏的常数太大,不够鲁棒

还有一种方案,使用一个hash表,对数组进行一趟扫描统计每个元素出现的次数,即可得到多数元素。时间复杂度O(n),空间复杂度O(n)。

像上面这样的算法,遇到全民公投这种大规模数据的处理需求,相当无力、虚弱,等待的时间太久了,以至于引起选民不满,弹性云计算按资源开销收费,老板也不开心。

我们发现这样一个规律,从序列中同时除去两个不同元素,类似于抵消,多数元素仍然占多数。假设A=[8, 8, 8, 2, 0, 8, 7, 10, 2, 8, 7, 8, 8, 8, 2],取c=A[0],经过一次抵消→[2, 8, 7, 8, 8, 8, 2],c=2,这个抵消这样设计,如果遇到相同的,就算到一起,不同的就减去一个计数,当计数count为0,意味着前面全部抵消完了,而不是按规律中僵化地非得找一个不同数据一起移除,→[7, 8, 8, 8, 2] →[8, 8, 2] →返回8,其它元素湮灭后剩余的“众数”。然后遍历一遍A,看count是否大于7,是的话就是多数元素。

这就是这个更漂亮的算法,Θ(n)和Θ(1)的时间和空间复杂度。

(三) 编程实现

编程语言Python3.5,依赖random库生成数据。

n=len(A) # 序列长度

def candidate(m):
        j=m # 工作索引
        c=A[m] # 候选/被比较的候选众数
        count=1# 计数器

        while j<n and count >0:
            j+=1
            # 如果现在的j使得列表越界,可以执行else的操作并退出循环
            try:
                A[j]
            except IndexError:
                count -= 1
                continue
            # 有一个一样计数器+1
            if A[j]==c:
                count+=1
            # 不一样的抵消,计数器-1
            else:
                count-=1
        # 工作索引和长度一样,走完序列,返回当前剩余的候选众数
        if j==n:
            return c
        else:
            return candidate(j+1)
def MAJORITY():
        c=candidate(0)
        count=0
        # 众数的出现次数
        for j in range(0,n):
            if A[j]==c:
                count+=1
        # 是否超过一半
        if count>(n/2):
            return c
        else:
            return None
        pass

(四) 举例演示递归过程

图一

中间3个变量A,n分别为传入数据和长度。

图 1

图二

c,count,j,m分别为候选数,计数器,工作索引,初始索引。

c=A[m],count由于前面一段(0~9)10个抵消变为0,所以递归调用下一层,下一层的初始工作索引j+1。

图 2

图三

可以看到和上一个格式一样,状态一样,即将进入“黑洞”。

图 3

图四

这一层j和n相等为16,结束遍历,所以还剩下一个活口的候选值8(画圈的)将被返回到上一个“宇宙”,接下来就逐层返回。

图 4

(五) 总结与改进

通过投票问题我们研究并实践了寻找多数数的算法,更进一步,该算法可以用并行实现,其基本思想为对原数组采用分治的方法,把数组划分成很多段(每段大小可以不相同),在每段中计算出candidate-count二元组,然后得到最终结果。

举个例子,原数组为[1,1,0,1,1,0,1,0,0] 划分1:
[1,1,0,1,1] –> (candidate,count)=(1,3)

划分2:[0,1,0,0] –> (candidate,count)=(0,2) 根据(1,3)和(0,2)可得,原数组的多数元素为1。

正因为这个特性,考虑若要从一个非常大的数组中寻找多数元素,数据量可能多大数百G,那么我们甚至可以用MapReduce的方式来解决这个问题。

参考资料

  1. http://blog.csdn.net/kimixuchen/article/details/52787307

  2. https://gregable.com/2013/10/majority-vote-algorithm-find-majority.html

  3. The Boyer-Moore Majority Vote Algorithm

  4. Finding the Majority Element in Parallel

所有代码

import random
'''
A为有n个整数元素的列表
'''
choose=1# 0:生成数据 1:全部运算 2:调试递归(需要用IDE)

all_A=[
    [8, 8, 8, 2, 0, 8, 7, 10, 2, 8, 7, 8, 8, 8, 2],
    [9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 9, 3, 9, 7, 3, 9],
    [6, 2, 2, 4, 2, 3, 2, 2, 5, 2, 2, 2, 2, 6, 2, 2, 5, 2],
    [8, 8, 8, 8, 6, 8, 5, 6, 0, 0, 4, 4, 8, 6, 8, 8],
    [3, 4, 10, 10, 4, 4, 4, 7, 4, 4]
   ]
def candidate(m):
            j=m # 工作索引
            c=A[m] # 候选/被比较的候选众数
            count=1# 计数器

            while j<n and count >0:
                j+=1
                # 如果现在的j使得列表越界,可以执行else的操作并退出循环
                try:
                    A[j]
                except IndexError:
                    count -= 1
                    continue
                # 有一个一样计数器+1
                if A[j]==c:
                    count+=1
                # 不一样的抵消,计数器-1
                else:
                    count-=1
            # 工作索引和长度一样,走完序列,返回当前剩余的候选众数
            if j==n:
                return c
            else:
                return candidate(j+1)
            pass

def MAJORITY():
    c=candidate(0)
    count=0
    # 众数的出现次数
    for j in range(0,n):
        if A[j]==c:
            count+=1
    # 是否超过一半
    if count>(n/2):
        return c
    else:
        return None
    pass

if choose==1:
    for A in all_A:
        # 序列长度
        n=len(A)
        # print(n)
        
        print(MAJORITY())

if choose==0:
    A=[]
    for i in range(5):
        b=[]
        for j in range(5):
            r=random.randint(0,10)
            for c in range(random.randint(1,3)):
                b.append(r)
        A.append(b)
    for i,a in enumerate(A):
        t = a[random.randint(0,len(a)-1)]
        for j in  range(len(a)//2):
            a.append(t)
        a=random.shuffle(a)
    for a in A:
        print(str(a)+',')

if choose==2:
    A=all_A[3]
    del all_A
    n=len(A)
    print(MAJORITY())

'''
8
9
2
None
4
'''
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容