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

news/2024/7/7 21:32:14

目录

  • 前言
  • 问题介绍
  • 解决方案
  • 代码编写
    • 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源码可邮件给2260755767@qq.com
再次感谢左大神对我算法的指点迷津!


http://lihuaxi.xjx100.cn/news/1023796.html

相关文章

第一章 初识NANO板卡

有人说&#xff1a;一个人从1岁活到80岁很平凡&#xff0c;但如果从80岁倒着活&#xff0c;那么一半以上的人都可能不凡。 生活没有捷径&#xff0c;我们踩过的坑都成为了生活的经验&#xff0c;这些经验越早知道&#xff0c;你要走的弯路就会越少。 本文链接:第一章 初识NANO…

【剑指offer-C++】JZ82:二叉树中和为某一值的路径(一)

【剑指offer-C】JZ82&#xff1a;二叉树中和为某一值的路径[一]题目描述解题思路题目描述 描述&#xff1a;给定一个二叉树root和一个值 sum &#xff0c;判断是否有从根节点到叶子节点的节点值之和等于 sum 的路径。 1.该题路径定义为从树的根结点开始往下一直到叶子结点所经…

Java并发(二)----初次使用多线程并行提高效率

1、并行 并行代表充分利用多核 cpu 的优势&#xff0c;提高运行效率。 想象下面的场景&#xff0c;执行 3 个计算&#xff0c;最后将计算结果汇总。 计算 1 花费 10 ms ​ 计算 2 花费 11 ms ​ 计算 3 花费 9 ms ​ 汇总需要 1 ms 如果是串行执行&#xff0c;那么总共花费的…

常用环境部署(七)——Docker安装RocketMQ

1、创建namesrv服务 &#xff08;1&#xff09;拉取镜像 docker pull rocketmqinc/rocketmq&#xff08;2&#xff09;创建一个数据目录 即创建一个namesrv数据存储路径 mkdir -p /docker/rocketmq/nameserver/logs /docker/rocketmq/nameserver/store&#xff08;3&#x…

Redis(四)事务 multi、exec

哈喽&#xff0c;大家好&#xff0c;我是有勇气的牛排&#xff08;全网同名&#xff09;&#x1f42e;&#x1f42e;&#x1f42e; 有问题的小伙伴欢迎在文末评论&#xff0c;点赞、收藏是对我最大的支持&#xff01;&#xff01;&#xff01;。 文章目录1 前言1.1 什么是Redi…

【Android平板编程】远程Ubuntu服务器code-server编程写代码

文章目录前言1.ubuntu本地安装code-server2. 安装cpolar内网穿透3. 创建隧道映射本地端口4. 安卓平板测试访问5.固定域名公网地址5.结语前言 本次教程将在 Ubuntu 服务器环境下安装 code-server &#xff0c;并使用 Android 安卓平板远程 Ubuntu 服务&#xff0c;进行远程编程开…

《剪花布条》:从花布条中尽可能剪出几块小饰条

目录 一、题目 二、思路 1、代码中要使用的String类中的方法 &#xff08;1&#xff09;判断 s 中是否有 t &#xff08;2&#xff09;将 s 分割 2、递归判断 三、代码 详细注释版本 简化注释版本 一、题目 题目&#xff1a;剪花布条 题目链接&#xf…

hjr-高并发系统如何保障高可用

高并发 高并发指的是 分布式系统中 并行处理的能力 在WEB开发中&#xff0c;两种情况会遇到高并发 1、TO C系统中 海量的用户请求 2、TO B系统中 海量的终端数据上报 分布式与集群 分布式系统与集群系统是有区别的 分布式指的是不同的节点负责不同的功能 集群指的是相同…