等概率随机函数设计技巧

news2024/9/19 7:30:23

文章目录

  • 1. 关于Math.random()函数
  • 2. 用1 ~ 5的随机函数加工出1 ~ 7的随机函数
  • 3. LeetCode 470. 用 Rand7() 实现 Rand10()
  • 4. 把不等概率随机函数变成等概率随机函数
  • 5. 用a ~ b的随机函数加工出c ~ d的随机函数

1. 关于Math.random()函数

Java 中 Math.random() 函数是等概率返回区间 [0,1) 中的任意一个浮点数, 即 x < 1 情况下, 调用这个api, 得到 [0,x) 范围上的数的概率就是 x; 那么, 如果想要将 x < 1 情况下, 生成的[0,x) 范围上的数的概率调整成 x ^ 2, 应该怎么实现?

  • 实现思路:

由于生成 [0,x) 的概率是 x , 那么调用两次 Math.random(), 得到的两个值中, 如果较大的那个值也在[0,x)区间内, 那么此时两次调用生成 [0,x) 范围上的数字的概率就为 x ^ 2了; 要注意这两次调用都必须在 [0,x) 区间内 (因为任意一次在 [x,1) 都会导致返回值不在 [0,x) 上).

  • 代码实现:
/**
 * rangom 函数生成的任意一个随机数都在 [0,1) 范围内,
 * 这个函数让 [0, x) 范围上出现的数字概率由原来的 x 调整为 x^2
 */
public static double xToXPower2() {
    // 调用两次,取到两次的的最大值,这样要想得到[0, x)范围内值就需要两次调用都在范围内
    // 一次得到的概率是x, 那么两次都要得到范围内的值的概率就是 x^2;
    return Math.max(Math.random(), Math.random());
}
  • 测试代码:
public static void main(String[] args) {
    // 测试次数
    int testTimes = 10000000;
    // random函数的测试
    int count = 0;
    double x = 0.27;
    for (int i = 0; i < testTimes; i++) {
        if (xToXPower2() < x) {
            count++;
        }
    }
    // 以下两个数值应该大小接近一致
    System.out.println(count / (double) testTimes);
    System.out.println(Math.pow(x, 2));
}
  • 测试结果:

img

可以看到结果是相近的.

2. 用1 ~ 5的随机函数加工出1 ~ 7的随机函数

假设我们有一个随机函数 f(), 这个函数可以等概率返回 [1,5] 中的一个数, 请实现只利用 f() 函数而不引入其他随机函数, 得到一个等概率返回 [1,7] 中任意一个数的函数.

  • 实现思路:

假设实现的目标函数为 c(), 由于目标是求 [1,7] 等概率返回一个, 如果我们能加工得到一个b()函数, 这个函数是等概率返回 [0,6] 范围内的任意一个数, 那么目标函数 c() 只需要调用这个 b() 函数再加上 1, 就可以等概率返回 [1,7] 中任意一个数, 即 c() 函数.

// 等概率返回 [1,7] 中任意一个数
public static int c() {
    // b()函数是等概率返回 [0,6] 中的任意一个数
    // 在获取b()的基础上再加1就是等概率返回1~7了
    return b() + 1;
}

接下来问题就变成了 b() 这个函数的实现, 要实现 [0,6] 等概率返回一个数, 思路如下:

先得到一个 0 和 1 等概率返回的随机函数 a(),

可以通过题目中给的 f() 函数来加工得到 a(),

f() 是 [1,5] 等概率返回的一个数, 也就是说, 调用 f() 会等概率返回 1,2,3,4,5 中的一个数, 我们约定,

当调用 f(), 得到 1 或者 2 的时候, 返回 0;

当调用 f(), 得到 4 或者 5 的时候, 返回 1;

当调用 f(), 得到 3 的时候, 什么都不返回, 再次调用 f().

代码如下:

// 利用[1,5]随机函数f() 生产等概率返回 0 和 1 的函数 a()
public static int a() {
    // 约定拿到1和2就代表0, 拿到4和5就代表1
    // 拿到的是3就重新获取
    int answer = 0;
    do {
        answer = f();
    } while (answer == 3);
    return answer < 3 ? 0 : 1;
}

这样就得到了 0 和 1 等概率返回的随机函数a(),

由于 6 的二进制表示是 110, 所以区间 [0,6] 的所有整数只需要 3 个二进制位就可以表示,

调用三次 a() 函数, 就可以等概率得到 [0,7] 范围内任意一个数 (因为区间[0,7]用二进制表示就是 000~111),即 [0,1,2,3,4,5,6,7] 等概率返回一个,

在得到 7 的时候, 重试上述过程, 只有结果在 [0,6] 才返回, 这样就加工出了 b() 函数, 即等概率返回 [0,6].

// 再利用a()生产能等概率拿到0到6的函数
public static int b() {
    // 三个二进制位就可以表示最大为6的数 (M N Y)
    int ans = 0;
    do {      // 
        ans = (a() << 2) // 得到 M
        + (a() << 1) // 得到 N
        + a(); // 得到 Y
    } while (ans == 7);
    return ans;
}

最后, 目标函数 c() 通过如下方式得到:

// 在获取b()的基础上再加1就是等概率返回1~7了
public static int c() {
    return b() + 1;
}
  • 完整代码实现:
/**
 * 使用1~5的随机函数加工出1~7的随机函数
 */
// 这个函数是等概率返回1~5的函数,只能使用,不能修改
public static int f() {
    // (int)[0,5) -> [0,4] -> +1 -> [0,5]
    return (int)(Math.random() * 5) + 1;
}
// 利用[1,5]随机函数f() 生产等概率返回 0 和 1 的函数 a()
public static int a() {
    // 约定拿到1和2就代表0, 拿到4和5就代表1
    // 拿到的是3就重新获取
    int answer = 0;
    do {
        answer = f();
    } while (answer == 3);
    return answer < 3 ? 0 : 1;
}
// 再利用a()生产能等概率拿到0到6的函数
public static int b() {
    // 三个二进制位就可以表示最大为6的数 (M N Y)
    int ans = 0;
    do {      //
        ans = (a() << 2) // 得到 M
                + (a() << 1) // 得到 N
                + a(); // 得到 Y
    } while (ans == 7);
    return ans;
}
// 在获取b()的基础上再加1就是等概率返回1~7了
public static int c() {
    return b() + 1;
}
  • 测试代码:

测试 10000000 次, 每次记录随机生成的数字, 得到每个数字出现的次数

public static void main(String[] args) {
    // 测试次数
    int testTimes = 10000000;
    // random函数的测试
    int count = 0;
    int[] counts = new int[8];
    for (int i = 0; i < testTimes; i++) {
        int num = c();
        counts[num]++;
    }
    System.out.println(Arrays.toString(counts));
}
  • 执行结果:

img

可以看到 1 到 7 这些数字出现的次数都差不多, 接近等概率.

和问题一的思想一样, f() 函数是等概率函数, 那么将 f() 多次调用得到的结果一样是等概率的, 调用 7 次 f() 相加, 得到的结果 % 7以后, 最后得到的一定是等概率返回 0~6 中任意一个整数的, 再加1, 就变成等概率返回 1~7 任意一个.

public static int g2() {
	return (f() + f() + f() + f() + f() + f() + f()) % 7 + 1;
}

3. LeetCode 470. 用 Rand7() 实现 Rand10()

给定方法 rand7 可生成 [1,7] 范围内的均匀随机整数, 试写一个方法 rand10 生成 [1,10] 范围内的均匀随机整数; 你只能调用 rand7() 且不能调用其他方法, 请不要使用系统的 Math.random() 方法.

在线OJ: LeetCode 470. 用 Rand7() 实现 Rand10()

  • 实现思路:

思路上面基本一致, 核心都是要先实现一个等概率返回 0 和 1 的随机函数 f(), 然后看目标函数区间需要几个二进制位, 来决定调用几次 f() 函数.

  • 代码实现:
/**
 * The rand7() API is already defined in the parent class SolBase.
 * public int rand7();
 * @return a random integer in the range 1 to 7
 */
class Solution extends SolBase {
    // 核心: 生成 0 和 1 等概率返回的随机函数
    public int f() {
        int val = rand7();
        while (val == 7) {
            val = rand7();
        }
        return (val <= 3) ? 0 : 1;
    }

    public int rand10() {
        int num = 0;
        do {
            num = 0;
            // 10 最多用4个二进制位表示即可
            for (int i = 0; i < 4; i++) {
                num += (f() << i);
            }
        } while (num < 1 || num > 10);
        return num;
    }
}

此外, 本题还有另外一种解法, 由 10 个 rand7() 之和得到的值 % 10 以后, 得到的一定是等概率返回 [0,9] 中的一个数, 再加 1, 就是等概率返回[1,10]了, 代码如下:

public int rand10() {
    return (rand7() + rand7() + rand7() + rand7() + rand7() + rand7() + 
            rand7() + rand7() + rand7() + rand7()) % 10 + 1;
}

4. 把不等概率随机函数变成等概率随机函数

有一个函数 x(), 不等概率 (但是是固定概率) 返回 0 和 1, 如何只通过 f() 函数, 得到等概率返回 0 和 1 的随机函数?

  • 实现思路:

调用两次 x() 函数, 可以得到如下情况

情况10 0
情况21 1
情况30 1
情况41 0

当两次都是 0, 或者两次都是 1 的时候, 即情况 1 和 情况 2, 就舍弃, 这样虽然 0 和 1 的概率不一样, 但是通过两次调用, 得到即情况 3 (0, 1)和 情况 4 (1, 0) 的概率一定是相同的, 所以我们可以约定,

所以, 我们可以约定, 两次调用得到的结果是 0 1 就返回 0, 得到的是 1 0就返回 1.

  • 代码实现:
public class Code_0003_EqualProbabilityRandom {

    // 不等概率函数,
    // 内部内容不可见
    public static int f() {
        return Math.random() < 0.8 ? 0 : 1;
    }

    // 等概率返回0和1
    public static int g() {
        int first;
        do {
            first = f(); // 0 1
        } while (first == f());
        return first;
    }


    public static void main(String[] args) {
        final int testTimes = 10000000;
        // count[0] 统计0出现的次数
        // count[1] 统计1出现的次数
        int[] count = new int[2];
        for (int i = 0; i < testTimes; i++) {
            count[g()]++;
        }
        System.out.println("0 出现的次数:" + count[0]);
        System.out.println("1 出现的次数:" + count[1]);
    }
}

/**
 * 提供当0和1以不同的概率返回, 设计函数让0和1以等概率返回
 */
public static int x() {
    return Math.random() < 0.66 ? 0 : 1;
}

// 等概率返回0和1
public static int y() {
    // 两次获取01或者10就是等概率的
    // 两次获取到11或者是00就是不等概率的,舍弃
    int ans = 0;
    do {
        ans = x();
    } while (ans == x());
    return ans;
}
  • 测试代码:

调用 10000000 次, 记录 1 和 0 出现的次数.

public static void main(String[] args) {
    // 测试次数
    int testTimes = 10000000;
    int[] arr = new int[2];
    for (int i = 0; i < testTimes; i++) {
        int num = y();
        arr[num]++;
    }
    System.out.println(Arrays.toString(arr));
}

测试结果:

img

可以看到 1 和 0 实际出现的次数差不多, 是接近等概率的.

5. 用a ~ b的随机函数加工出c ~ d的随机函数

如何用一个生成区间 a ~ b 的随机函数加工生生产出成 c ~ d 的随机函数.

  • 实现思路:

核心思路与前面的问题是一样的, 都是构造等概率生成 0 和 1 的 rand() 函数, 然后通过 rand()函数加工出随机生成 c ~ d 的函数.

假设生成区间 a ~ b 的随机函数是f(a,b),

如果 a ~ b 的区间元素个数是偶数, 那么将区间分两半, 如果得到的是前面一半的数, 则返回 0; 如果得到后面一半的数, 则返回 1;

如果 a ~ b 的区间元素个数是奇数, 那么把将区间分两半, 如果得到的是前面一半的数, 则返回 0; 如果得到后面一半的数, 则返回 1; 得到中间位置的数, 则舍弃, 然后继续调用;

这样一来, 就加工出一个等概率随机生成 0 或 1 的函数 rand(), 然后就要判断要表示 1 ~ d - c + 1 这个范围的的值需要几个二进制(让 1<<i - 1 到大于范围即可), 不过由于这里的 c,d 并不是确定的值, 无法计算得到准确所需需要的二进制位数, 不过并不影响结果, 得到结果后判断结果是不是在范围内, 不在的话接着重新调用获取即可, 这样就可以得到随机生成 c ~ d 的函数了.

  • 代码实现:
// 构造一个等概率返回a~b的随机函数
public static int f(int a, int b) {
    // Math.random() -> [0, 1)
    // [a, b] -> a + [0,b-a] -> a + (int)(Math.random() * (b-a+1))
    return a + (int) (Math.random() * (b - a + 1));
}

// 要根据f()加工出等概率返回0和1的随机函数
public static int rand(int a, int b) {
    // a = 4, b = 8
    // countNum = 5
    int countNum = b - a + 1; // 可能出现的随机数的个数
    // isOdd = true
    // 是否为奇数个
    boolean isOdd = countNum % 2 != 0;
    int midNum = (a + b) / 2;
    int ans;
    do {
        // 如果是奇数, 那么中点位置弃用, < 中点 位置 和 > 中点位置的数字返回概率一样, 一个定为0, 一个定为1
        // 如果是偶数, 那么这里的中点是上中点, < 上中点 位置 和 >= 上中点位置的数字出现的概率一样, 一个定为0, 一个定为1即可
        ans = f(a, b);
    } while (isOdd && ans == midNum);

    // 如果是偶数,ans可能会等于midNum
    // 如果是奇数,ans必不等于midNum
    return ans <= midNum ? 0 : 1;
}

// 加工出等概率返回c~d的随机函数
public static int g(int a, int b, int c, int d) {
    int range = d - c; // 0~range + c~d -> c~d
    int count = 1;

    while ((1 << count) - 1 < range) { // 求0~range需要几个2进制位
        count++;
    }
    int ans;
    do {
        ans = 0;
        for (int i = 0; i < count; i++) {
            ans += (rand(a, b) << i);
        }
        // 要注意这里的实现, 由于这里 c,d 是随机给出的, 也就不好准确计算出所需要的进制位
        // 所以, 要判断最终的结果是不是在范围内, 不在的话要重新调用获取
    } while (ans > range);
    return ans + c;
}
  • 测试代码:
public static void main(String[] args) {
    // 测试次数
    int testTimes = 10000000;
    int[] array = new int[20];
    for (int i = 0; i < testTimes; i++) {
        int num = g(3, 19, 20, 39);
        array[num-20]++;
    }
    System.out.println(Arrays.toString(array));
}
  • 测试结果:

img

可以看到每个数实际出现的次数是差不多的, 接近等概率.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/489195.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微服务和领域驱动

一、微服务 1.1 什么是微服务 微服务就是一些协同工作的小而自治的服务。 关键词&#xff1a; 小而自治 -- 小 “小”这个概念&#xff0c;一方面体现在微服务的内聚性上。 内聚性也可以称之为单一职责原则&#xff1a;“把因相同原因而变化的东西聚合到一起&#xff0c;…

python能成为编程届的网红么?

Python本身就是编程语言届的网红&#xff0c;Python&#xff0c;年龄可能比很多读者都要大&#xff0c;但是它在更新快速的编程界却一直表现出色&#xff0c;甚至有人把它比作是编程界的《葵花宝典》&#xff0c;只是Python的速成之法相较《葵花宝典》有过之而无不及。 但是能…

短视频矩阵系统.代码实时分享

短视频矩阵系统核心技术研发是为满足现代社交网络时代用户对视频分享和观看的需求而推出的一项技术。它旨在提供高质量的视频传输、智能推荐算法、实时互动等功能。短视频矩阵系统设计上考虑了多个关键技术&#xff0c;包括多媒体编解码技术、网络通讯技术、机器学习算法等。通…

[pgrx开发postgresql数据库扩展]附.更新开发环境安装脚本

pgrx更新到0.83之后&#xff0c;我本来还没感觉&#xff0c;但是我五一放假一来&#xff0c;发现我的WSL环境居然就挂了…… 果然是非稳定版本就是不靠谱了…… 所以我干脆搞了个虚拟机&#xff0c;重新安装了一套&#xff0c;还别说&#xff0c;更新到了0.83之后&#xff0c;安…

(十一)地理数据库创建——创建新的地理数据库

地理数据库创建——创建新的地理数据库 目录 地理数据库创建——创建新的地理数据库 1.地理数据库概述2.地理数据库建立一般过程2.1地理数据库设计2.2地理数据库建立2.2.1从头开始建立一个新的地理数据库2.2.2移植已经存在数据到地理数据库2.2.3用CASE工具建立地理数据库 2.3建…

学习HCIP的day.04

目录 七、关于OSPF的不规则区域问题 1、通过隧道链路&#xff08;Tunnel&#xff09; 2、OSPF的虚链路配置 3、多进程双向重发布 八、OSPF的数据库表 九、OSPF优化 1、汇总 2、特殊区域 --- 用于减少各个非骨干区域的LSA数量 七、关于OSPF的不规则区域问题 分为两种情…

【MySQL实战2 作业解析】

这里写自定义目录标题 作业回顾作业步骤完成方法恢复数据库设置查询日志的开关删除退市股票以及新股的无效交易日的数据删除个股数据表查询merged_table这张表里3开头的股票中每个月成交量最大的股票下载日志文件&#xff0c;备份数据库 作业回顾 作业内容发布在社区里&#x…

输入捕获模式测频率、PWMI模式测频率占空比

一、知识点 TIM输入捕获模式&#xff1a; 1、输入捕获模式测频率占空比 信号源&#xff1a;产生一个频率和占空比可调的波形 无信号发生器的情况&#xff1a;先用PWM模块&#xff0c;在PA0端口输出一个频率和占空比可调的波形&#xff0c;把PA0和PA6连在一起&#xff0c;PA6为输…

Ubuntu 安装 Mysql

主要内容 本文主要是实现在虚拟机 Ubuntu 18.04 成功安装 MySQL 5.7&#xff0c;并实现远程访问功能&#xff0c;以 windows 下客户端访问虚拟机上的 mysql 数据库。 1. 切换至 root 用户 &#xff0c;shell 终端指令均执行在 root 用户下 sudo su 2. 安装并设置 mysql 安…

DOM操作-获取元素的方式

DOM—文档对象模型 ●DOM&#xff08;Document Object Model&#xff09;&#xff1a; 文档对象模型 ●其实就是操作 html 中的标签的一些能力 ●或者说是一整套操作文档流的属性和方法的集合 ●我们可以操作哪些内容 ○获取一个元素 ○移除一个元素 ○创建一个元素 ○向页面里…

Unity防破解方案解析

Unity作为游戏开发市场第一大游戏引擎占有者&#xff0c;已经全面覆盖到各个游戏平台&#xff0c;在全球范围内超过50% 的手机游戏、PC 游戏和主机游戏都使用 Unity 创作而成。 同时&#xff0c;Unity也是中国游戏公司的首选开发引擎&#xff0c;《原神》《王者荣耀》《英雄联盟…

【MySQL入门指南】4种插入数据的方法

文章目录 MySQL的增删查改① - 增1.发生冲突则失败1.1 基本语法1.2 单行数据全列插入1.3 多行数据 指定列插入 2.发生冲突则更新2.1 基本语法2.2 插入否则更新 3.发生冲突则替换3.1 基本语法3.2插入否则替换 4.插入查询结果 MySQL的增删查改① - 增 -- 创建一张学生表 CREATE…

狂神 springcloud学习

springcloud学习 笔记整理来源 B站UP主狂神说https://www.bilibili.com/video/BV1jJ411S7xr 参考&#xff1a;菜鸟小杰子 https://blog.csdn.net/lijie0213/article/details/107914619 参考&#xff1a;路飞 https://csp1999.blog.csdn.net/article/details/106255122?spm100…

stm32cubemx配置mpu6050——10分钟0基础到灵活使用

stm32cubemx配置mpu6050——10分钟0基础到灵活使用 10分钟速通&#xff01;你没看错&#xff0c;就是10min&#xff0c;从0基础到灵活运用mpu6050。 不信&#xff1f;往下看看就会&#xff1a;嗷~原来如此 第一步 下载github开源代码。 https://github.com/leech001/MPU6050首…

从零开始学习Linux运维,成为IT领域翘楚(六)

文章目录 &#x1f525;Linux磁盘管理&#x1f525;Linux挂载硬盘&#x1f525;Linux系统状态检测命令&#x1f525;Linux下载软件安装的命令 &#x1f525;Linux磁盘管理 分区的方式 &#x1f41f; MBR分区表&#xff1a;每块硬盘最大支持2.1TB硬盘&#xff0c;每块硬盘最多支…

《编程思维与实践》1042.字串变换

《编程思维与实践》1042.字串变换 题目 思路 分两步解决&#xff1a; 1.判断是否可以通过两种变换使所有的字符串变得相同&#xff1b; 2.在能变换的前提下使变换的次数最少。 其中第一步可以将每个字符串的基底(将连续重复出现的字符视为一个字符)求出来, 如: aaabbb的基底就…

28《Protein Actions Principles and Modeling》-《蛋白质作用原理和建模》中文分享

《Protein Actions Principles and Modeling》-《蛋白质作用原理和建模》 本人能力有限&#xff0c;如果错误欢迎批评指正。 第七章&#xff1a;Proteins Evolve &#xff08;蛋白进化&#xff09; 蛋白质分子可以通过生物进化而发生改变。随着生物体的进化&#xff0c;它们…

Haproxy集群

引言 Haproxy 是一个使用C语言编写的自由及开放源代码软件&#xff0c;其提供高可用性、负载均衡&#xff0c;以及基于TCP和HTTP的应用程序代理。 一、Haproxy简介 1、Haproxy应用分析 LVS在企业中康复在能力很强&#xff0c;但存在不足&#xff1a; LVS不支持正则处理&…

Java编程中的20种常见异常及其原因,你知道多少

本文介绍了在Java编程中可能遇到的20种常见异常&#xff0c;包括空指针异常、类未找到异常、数组下标越界异常等&#xff0c;并简要解释了每种异常发生的原因。这些异常可能由于编程错误、运行时资源不足或权限受限等多种原因触发&#xff0c;了解它们有助于更高效地进行程序调…

Crowdsoure的简单介绍

一、什么是Crowdsoure 在美国《连线》杂志2006年的6月刊上&#xff0c;记者Jeff Howe首次提出了Crowdsoure&#xff08;众包&#xff09;的概念。众包是一个框架&#xff0c;它将大量分散的人群聚集在一起&#xff0c;收集数据&#xff0c;解决问题&#xff0c;或应对挑战。它…