• 算法浅谈——怎么样才最有可能选中真命天子呢?


    本文始发于个人公众号:TechFlow

    正文开始之前,我们先来讲一个故事。

    在很久很久以前,有一个万人迷。

    她从18岁开始就有数不完的追求者,追她的男生一个个在她的窗前排起了长队。但是她挑来挑去,终究不觉得满意。终于,这个万人迷一天天长大,年老色衰,在她门口排队的男生也越来越少。

    她开始后悔拒绝男生时的轻率,怀念起了从前的荣光。她也不知道,最后她是会向现实妥协,选择一个看起来远不是那么好的男生共度一生,还是会就这么一直等下去。

    这样的故事其实并不罕见,知乎里关于剩男剩女以及婚嫁的问题屡见不鲜。选择配偶也是我们人生当中的必经之路,苏格拉底说过,人生就是一次无法重复的选择,在婚姻这个问题上尤为明显。

    那么问题来了,如果我们是故事中的万人迷,我们应该如何选择配偶呢?

    即使是真的万人迷,她可以选择的配偶也一定是有限的。我们可以做一个简单的量化,假设她一年平均有30个追求者,她打算28岁结婚。那么从她18岁开始算起,假设她的魅力保持不变,她一共可以遇到300个潜在的配偶。

    这个数字对于每个女生而言各有不同,但是它其实并不重要,并不会影响我们的计算过程。为了简化计算,我们就假设它为n。接着,我们再进一步简化模型,假设这n个男生排成一队,一个一个地来发起追求。我们假设女生面临每个追求者的时候只会有两个选择,一是直接拒绝,二是答应追求,从此牵手共度一生。

    那么,我们来做一个好的决策呢?

    和现实中一样,一种比较聪明的做法是,先和前面的一些男生每个人都相处一段时间,做一个了解,摸清这些男生大概的水平底细之后再认真考虑。抽象成数学模型来,就是女生会直接拒绝掉前面k个男生,从第k+1个男生开始一一和前面k个男生比对。当一个比前面k个男生都要好的男生出现的时候,她果断选择接受,从此和他共度一生。

    如此一来,这就成了一个数学问题,究竟这个k应该等于多少,才可以使得女生选中所有男生当中最好的那个的概率最大呢?

    所以,我们应该怎么求出这个K呢?

    对于某个固定的K,我们假设最佳配偶出现在了第i的位置。想让他能被挑选中,必须要保证前面i-1个人中的最好的配偶出现在前K个人当中。也就是说如果真命天子前面没有出现另一个优质的男生,会导致女生在遇见真命天子前就草草选择。从这个问题上来说,真命天子也需要好的对手陪衬。

    这个概率不难计算,是:(frac{K}{i-1})

    那么,我们对所有的i进行加权求和即可:

    (P(K)=sum_{i=K+1}^nfrac{1}{n}cdot frac{K}{i-1}=frac{K}{n}sum_{i=K+1}^nfrac{1}{i-1})

    我们假设n是一个很大的值,我们可以先算后面的部分。如果n足够大,可以认为

    [sum_{i=K+1}^nfrac{1}{i-1}=int_{K}^nfrac{1}{t}dt=ln(n)-ln(K)=ln(frac{n}{K}) ]

    我们令(x=frac{K}{n})

    求积分,可以得到:

    (P(K)=x(ln(frac{n}{K}))=x(ln(frac{1}{x}))=-xcdot ln(x))

    我们对(P(K))的求导,令它等于0,可以求出(P(K))最大时(x=frac{1}{e})。这里的(e)就是数学当中经常出现的欧拉常数,也叫自然底数,(frac{1}{e})约等于37%. 那么,算到了这个结果,这个问题也就有了答案。

    如果你是一个万人迷,那么你应该拒绝掉前面37%的追求者,然后在剩下的63%的男士当中挑选一个比前面都强的作为配偶。那么你选到最佳配偶的概率达到最大值,它的概率为37%。

    虽然有了答案,但是我们并不知道这个答案对不对,但是没关系,我们是程序员,可以用代码来模拟。

    我们就按照万人迷的配置来设定好了,假设她一生当中会面临300个追求者。我们假设这三百个追求者的好坏层次不齐,按照分数排序,可以得到一个0到299的序号。排名越靠后,说明分数越大,男生越优质,然后我们再对这些男生进行乱序。

    import random
    
    def generateBoys():
        boys = [i for i in range(300)]
        random.shuffle(boys)
        return boys
    

    接着我们来编写程序的主体,其实也很简单,我们模拟进行许多次同样的配偶选择,模拟出我们通过这种策略能够选中最佳配偶的概率,代码并不难写:

    # iterations 是模拟择偶的次数
    def simulation(iterations=10000):
        matched = 0
        for i in range(iterations):
            # 每次都创建新的追求者集合
            boys = generateBoys()
            # 最佳配偶的序号
            best = max(boys)
            maxi = 0
            partner = 0
            # 计算K, K=0.37 * 追求者总数
            pickedNum = int(0.37 * len(boys))
            for j in range(pickedNum):
                maxi = max(maxi, boys[j])
            # 一旦找到比前K个最好的都要好的,就结束
            for j in range(pickedNum, len(boys)):
                if boys[j] > maxi:
                    partner = boys[j]
                    break
            # 判断是否找到了最佳配偶
            if partner == best:
                matched += 1
        return matched / float(iterations)
    

    最后,我们运行代码,得出的答案是0.3629。当然这也不是一个精确值,也是一个会波动的估算结果。迭代的次数越多,这个得到的结果越逼近真实值。用大量的实验去测算某个事件发生的概率,这个也是统计学上常用的方法。

    通过建模,我们把一个抽象的,无从下手的问题,简化成了一个明确的数学问题。通过建立函数求最值的方法,求出了最优解。从结果上来看,如果真有一个姑娘能有这么多追求者,通过一种方法可以拥有37%的概率挑中她的真命天子,也算是非常棒了。

    但是数学模型的是理想的,现实和理想总是有些差别。现实中,我们的时间精力是有限的,我们不一定有时间来一一衡量前面追求者的优劣。而且追求者的分布也不一定是随机的,很有可能随着我们自身的变化而变化。比如我们通过自己的努力,去往了更好的学校、公司,那么我们接触到的异性也会更好。

    不过尽管如此,这道算法问题对我们还是很有借鉴意义,希望能够给大家带来启发。

    今天的文章就到这里,希望大家有所收获。如果喜欢本文,请顺手点个关注吧。

  • 相关阅读:
    Python 规范
    Hql
    Python
    IIS 日志分析
    NHibernate 知识点整理
    微软开放了.NET 4.5.1的源代码
    自定义消息编码绑定实现
    使用自定义绑定
    WCF安全:通过 扩展实现用户名密码认证
    WCF 几种错误
  • 原文地址:https://www.cnblogs.com/techflow/p/12199491.html
Copyright © 2020-2023  润新知