一道数学题的思考

172 阅读7分钟

  最近看见一道数学题,比较有意思:

房间里有100个人,每人都有100元钱,他们在玩一个游戏。每轮游戏中,每个人都要拿出一元钱随机给另一个人,最后这100个人的财富分布是怎样的?我们不妨把这场游戏视作社会财富分配的简化模型,从而模拟这个世界的运行规律。我们假设:每个人在18岁带着100元的初始资金开始玩游戏,每天玩一次,一直玩到65岁退休。“每天拿出一元钱”可理解为基本的日常消费,“获得财富的概率随机”是为了……嗯……简化模型。以此计算,人一生要玩17000次游戏,即获得17000次财富分配的机会。答案有如下三种情况:

这里写图片描述

  当看见这道题的时候,我认为财富的分布应该是接近均匀分布,毕竟概率是均等的。但是答案竟然是接近冥律分布,为什么呢?我决定使用代码来计算一遍!

  使用代码计算,首先考虑一个问题,就是随机数,目前 Random 函数的随机数存在“伪随机”,就是每次的随机数都是一样的,我们可以通过代码来测试一下:

import java.util.Random;

public class MyClass {
    public static void main(String[] args) {
       
        for (int i = 0; i < 3; i++) {
            System.out.println( "第"+i+"次输出结果"+new Random(5).nextInt(100));
        }

    }
}

接下来看看日志结果,大家就明白了!

第0次输出结果87
第1次输出结果87
第2次输出结果87
第3次输出结果87
第4次输出结果87
第5次输出结果87
第6次输出结果87
第7次输出结果87
第8次输出结果87
第9次输出结果87

避免随机数的方法比较多,有减掉种子参数()、将 Random 对象生成一个对象,每次使用同一个对象,代码验证一下:

import java.util.Random;

public class MyClass {
    public static void main(String[] args) {
        Random rl = new Random();
        for (int i = 0; i < 10; i++) {
            System.out.println( "无种子参数第"+i+"次输出结果"+ new Random().nextInt(10));
            System.out.println( "同 Random 对象第"+i+"次输出结果"+ rl.nextInt(10));
        }

    }
}

日志验证结果:

无种子参数第0次输出结果4
同 Random 对象第0次输出结果6
无种子参数第1次输出结果0
同 Random 对象第1次输出结果1
无种子参数第2次输出结果8
同 Random 对象第2次输出结果8
无种子参数第3次输出结果4
同 Random 对象第3次输出结果4
无种子参数第4次输出结果1
同 Random 对象第4次输出结果8
无种子参数第5次输出结果7
同 Random 对象第5次输出结果6
无种子参数第6次输出结果8
同 Random 对象第6次输出结果4
无种子参数第7次输出结果8
同 Random 对象第7次输出结果2
无种子参数第8次输出结果7
同 Random 对象第8次输出结果8
无种子参数第9次输出结果0
同 Random 对象第9次输出结果1

上述的“伪函数”的原因是种子参数的使用——

在进行随机时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要的随机数字。因此,相同种子数的Random对象,相同次数生成的随机数字是完全相同的。

  了解了Random对象以后,接下来需要实现财富分配的过程,上面说的是规则不大清晰,只说了每个人的初始资金为100元,每次中奖了就分配,然后总次数为17000次。我们在实现的过程中需要考虑的问题还有几点:

  1. for 循环的时候,是 100 个人每次抽奖 17000 次,还是抽 17000 次奖,每次遍历 100 个人?
  2. 当每个人的资金减少到0元以后是否还有机会中奖?
  3. 当有人的资金减少为 0 时,可分配的奖金响应减少,如何纪录?
  4. 当问题 1 为否定时,会发生无人中奖的情况,这个时候已经出资1元的资金需要取回,且总次数是否增加?

  第一、虽然总的计算次数都是1700000次,但是这里的逻辑不一样,前者抽奖次数达到了1700000次,后者抽奖次数固定为17000次,按照逻辑判断,应该选后者。

  第二、我将这个问题理解为博弈,并非作者所提的社会问题(社会问题需要考虑到财富平均的财富再分配,博弈考虑的是我付出了有多大的收益),因此第一个问题我的答案是没有机会中奖,这样也会更加公平!

  第三、我的做法是在每轮博弈前设置一个奖金初始值 bonus,假设每次一开始都不知道有人需要退出,因此初始值一直是100元,接下来在每次收取博弈奖金的时候判断参与者是否还有资金(资金是否为 0 或者为 -1)参与本轮博弈,没有的话我会给他们一个标记(资金设为 -1),并且将奖金 bonus 减去 1 。当 100 人循环完毕以后,博弈奖金就是 bonus 了!

  第四、该问题类似第二个问题,在每轮博弈前设置一个标记 isBouns ,定义为无人中奖。然后在得到 bonus 以后,就可以开奖了。如果遇到无人中奖(假设中奖号为 99 ,但是 99 号没有投注博弈资金了,则本轮无人中奖),这个时候可以通过判断 isBouns 来决定总次数是否增加。由于总次数 17000 次是来自时间的计算,所以这里我也没有增加总次数了。如果需要增加,我们可以对总次数定义一个变量 22K (可以自行百度22k 这个故事),初始值为 17000 ,在无人中奖的时候加 1 即可。

代码如下:

import com.google.gson.Gson;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Created by κ?? on 2017/7/30.
 */

public class Test {
    static List<Integer> data = new ArrayList<>();
    public static void main(String []args) {
        Random rl = new Random();
        int count = 100;
        int all = 17000;


        for (int i = 0; i < 100; i++) {
            data.add(100);
        }
        int ran = rl.nextInt(100);
        for (int i = 0; i < 17000; i++) {
            boolean isBouns = false;
            int bonus = 100;
            for (int j = 0; j < 100; j++) {

                if(data.get(j) == 0){
                    data.set(j,-1);
                    bonus--;
                }else if(data.get(j) != -1){
                    int value = data.get(j);
                    data.set(j,value-1);
                }else{
                    bonus--;
                }

            }
            if(data.get(ran)!=-1){
                int value = data.get(ran);
                data.set(ran,value+bonus);
                isBouns = true;

            }

            ran = rl.nextInt(100);
            if(!isBouns){
                for (int j = 0; j < 100; j++) {
                    int value = data.get(j);
                    if(value!=-1){
                        data.set(j,value+1);
                    }

                }
            }
            


        }


        System.out.println(getData());
        //数据详情
        System.out.println(new Gson().toJson(data));
    }

    private static int getData() {
        int result = 0;
        int count = 0;
        int winer = 0;
        for (int i = 0; i < 100; i++) {
            if(data.get(i) != -1 && data.get(i) != 0){
                result+=data.get(i);
                count++;
                //绝对赢家
                if(data.get(i)>100){
                    winer++;
                }
            }
        }
        System.out.println("共有"+winer+"人赢了!其中有"+count+"还有钱!");
        //总资金 10000,检查这个值计算是否有计算出错
        return result;
    }
}

  最后说一下我的计算结果:

  代码运行10 次,累计208人还有钱,其中7人不足100元,也就是说平均每轮(几十年的概念)有20.8%的概率还有能力继续博弈,每轮有20.1%的概率盈利!


参考文章:

blog.csdn.net/hla199106/a…


文章来源

media.weibo.cn/article?id=…