【算法】【算法杂谈】已知[1,m]的等概率函数,求[1,n]的等概率函数

目录

  • 前言
  • 问题介绍
  • 解决方案
  • 代码编写
    • java语言版本
    • c语言版本
    • c++语言版本
  • 思考感悟
  • 写在最后

前言

当前所有算法都使用测试用例运行过,但是不保证100%的测试用例,如果存在问题务必联系批评指正~

在此感谢左大神让我对算法有了新的感悟认识!

问题介绍

原问题
给定一个等概率随机1-5的函数,在不使用任何额外的随机函数下,通过rand1To5实现rand1To7函数
补充题目
给定一个以p概率产生0,1-p概率产生1的随机函数rand01P,通过该函数实现等概率的rand1To6
进阶题目
给定一个随机产生1-M的随机函数rand1ToM,请依次来实现rand1ToN的随机函数

解决方案

原问题
1、首先求出等概率的0-4,即rand1To5()-1
2、再求出等概率的[0,5,10,15,20],即 (rand1To5()-1)* 5 (扩容)
3、此时可以求出等概率的0-24,即 (rand1To5()-1)* 5 + rand1To5()-1 (插空)
4、再mod6即可得到0~6的等概率,再+1即可得到1-7的等概率函数了!
最后公式:((rand1To5()-1)* 5 + rand1To5()-1) % 6 + 1
补充题目
1、首先需要获取一个能够出现等概率的函数,才能解决等概率的问题
2、首先0和1无法一次调用出现等概率,但是调用两次,可能的结果为 00(概率:p^2) 01(概率:p(1-p))10(概率:p(1-p)) 11((1-p) ^2),由此发现10和01的出现概率相同!利用这个即可获取一个01的等概率函数
3、如果有了等概率的01,则能通过原问题中的解法就可以求的等概率的05,也就能求出等概率的1-6了
进阶题目
1、首先将n-1转化成m进制的数
2、申请一个长度为32位的int类型数组,作为m进制的结果
3、从左向右进行等概率的获取0-(m-1)的数,也就是高位先等概率生成,如果中途发现整体数大于0了,则直接放弃,从头开始
4、最终获取到的结果再转成10进制即可

代码编写

java语言版本

原问题:
方法一:

     /**
     * 随机返回1到5之间的数
     * @return
     */
    public static int random1to5Cp1() {
        return (int) (Math.random() * 5 + 1);
    }

    /**
     * 以p的概率出现0和1
     * @param p
     * @return
     */
    public static int randomP01Cp1(double p) {
        return Math.random() < p ? 0 : 1;
    }


    /**
     * 随机返回1到7之间的数
     * 要求:1、仅使用random1to5实现2、必须等概率
     * 方法:插空法、筛选法
     * @return
     */
    public static int random1to7Cp1() {
        int p = 0;
        while ((p = (random1to5Cp1() - 1) * 5 + (random1to5Cp1() - 1)) > 20) {
            p = (random1to5Cp1() - 1) * 5 + (random1to5Cp1() - 1);
        }
        return (int)(p%7) + 1;
    }

    /**
     * 等概率实现1到6的随机函数
     * 要求1、仅使用randomP01Cp1实现2、等概率
     * @return
     */
    public static int random1to6Cp1() {
        // 0-6
        int p = 0;
        while ((p = get03Cp1()*2 + (get01Cp1() + 1)) > 6) {
            p = get03Cp1()*2 + (get01Cp1() + 1);
        }
        return (int)(p%6) + 1;
    }

    /**
     * 将不等概率的变成等概率返回0和1
     * @return
     */
    private static int get01Cp1() {
        int num1 = 0, num2 = 0;
        while (((num1 = randomP01Cp1(0.83)) ^ (num2 = randomP01Cp1(0.83))) != 1);
        return num1;
    }

    /**
     * 等概率获取0123
     * @return
     */
    private static int get03Cp1() {
        return get01Cp1() * 2 + get01Cp1();
    }


    /**
     * 给定一个m,返回1-m等概率出现
     * @param m
     * @return
     */
    private static int get1ToM(int m) {
        return (int)(Math.random() * m) + 1;
    }

    /**
     * 通过等概率的1-m,求等概率的1-n
     * @param m
     * @param n
     * @return
     */
    private static int get1TonFromM(int m, int n) {
        // 首先求用m进制表示的n-1
        int[] mSysN = getSysMNum(n-1, m);
        // 产生一个小于mSysN的m进制的数
        int[] mSys1ToN = getSysM1ToN(mSysN, m);
        // 再转回到10进制
        return get10SysFromM(mSys1ToN, m) + 1;
    }

    /**
     * 从m进制转回到10进制
     * @param mSys1ToN
     * @param m
     * @return
     */
    private static int get10SysFromM(int[] mSys1ToN, int m) {
        int index = mSys1ToN.length-1;
        int res = 0;
        while (index >= 0) {
            res += Math.pow(m, (mSys1ToN.length-1) - index) * mSys1ToN[index];
            index--;
        }
        return res;
    }

    /**
     * 等概率随机获取一个小于mSysN的值
     * @param mSysN
     * @param m
     * @return
     */
    private static int[] getSysM1ToN(int[] mSysN, int m) {
        // 找到左边第一个不为0的
        int start = 0;
        while (mSysN[start] == 0) {
            start++;
        }
        // 生成数游标
        int index = start;
        int[] res = new int[32];
        // 表示上一个值是否和mSysN相等,如果相等,则这波还要继续生成,如果不相等说明已经大于了直接随机生成即可
        boolean isLastEquals = true;
        // 从高位开始随机获取
        while (index != mSysN.length) {
            // 先生成
            res[index] = get1ToM(m)-1;
            // 判断该位置的上一个是否相等
            if (isLastEquals) {
                // 相等则该位置需要比较
                if (res[index] > mSysN[index]) {
                    // 说明生成的超过了,归位,全部重来
                    index = start;
                    isLastEquals = true;
                    continue;
                }else {
                    isLastEquals = res[index] == mSysN[index];
                }
            }
            // 该位置不需要比较
            index++;
        }
        return res;
    }

    /**
     * 用m进制表示value
     * @param value
     * @param m
     * @return
     */
    private static int[] getSysMNum(int value, int m) {
        int[] res = new int[32];
        int mod = value;
        int tem = value;
        int index = res.length-1;
        while (tem != 0) {
            res[index--] = mod % m;
            tem = mod/m;
            mod = mod%m;
        }
        return res;
    }



    public static void main(String[] args) {
        int[] ints = new int[5];
        for (int i = 0; i < 100 ; i++ ){
            //System.out.println(get1TonFromM(7, 5));
            ints[get1TonFromM(7,5)-1] ++;
            //System.out.println();
        }
        CommonUtils.printArr(ints);
        //System.out.println(String.format("%.2f", 0.0124124124));
    }

c语言版本

正在学习中

c++语言版本

正在学习中

思考感悟

1、原问题主要讲述了一个思想就是如果用小范围的等概率扩容到大范围的等概率,其中先用乘法扩容,扩容不是随便扩容的,扩容的大小刚还要能够让自己插空,也就是说 0-4的等概率扩容必须是0,5,10,15,20,只有这样才能将0-4插入到空隙中形成等概率的0-24,之后通过mod来缩小范围即可
2、补充问题主要讲述了如何通过非等概率获取等概率的方法,那就是通过两次的组合发现等概率的事件来制造等概率函数
3、最后一道题看似是原问题的拓展版本,其实实现方式反而是另一种角度来看这类问题了,进阶问题其实讲述了该类问题可以通过进制方式来解决,首先将目标变成m进制,之后用0-m-1的随机来生成进制的每一位,并不断判断是否越界,直到没有越界生成一个符合的数之后返回这个数,整体上还是有序的。

写在最后

方案和代码仅提供学习和思考使用,切勿随意滥用!如有错误和不合理的地方,务必批评指正~
如果需要git源码可邮件给[email protected]
再次感谢左大神对我算法的指点迷津!

你可能感兴趣的:(java专栏,算法,阅读随笔,算法)