玩转Random的正确姿势

一、关于java.util.Random

我们知道,在数学领域里面0到1之间的小数是无穷无尽的,所以如果从数学角度上来讲,要计算0到1之间某个小数出现的概率是不现实的,但是作为计算机领域的人员应该会注意到,大多数编程语言中随机数的出现是等概率的,为什么会这样呢,因为在计算机里面,是有精度限制的,所以能表示的小数范围是有限的,而不是无穷无尽的,那么各个语言在设计的时候,通过一定的设计就可以实现等概率返回某个小数,下面以java语言为例

在 Java 中,java.util.Random 类的随机数生成是基于伪随机数生成器(Pseudorandom Number Generator,PRNG)实现的。PRNG 是一种算法,通过使用一个种子(seed)来生成一系列看似随机的数字序列。这些数字序列在统计上表现为随机分布,但实际上是确定性的,因为它们是根据初始种子和固定的算法生成的。

java.util.Random 类使用一个称为线性同余生成器(Linear Congruential Generator,LCG)的算法来生成伪随机数。LCG 算法通过以下公式生成随机数序列:

其中:

  • (X_n) 是前一个随机数
  • (X_{n+1}) 是下一个随机数
  • (a)、(c) 和 (m) 是算法中的参数

在 java.util.Random 类中,这些参数被固定为以下值:

  • (a = 25214903917)
  • (c = 11)
  • (m = 2^{48})

Random 类的 nextDouble() 方法会生成一个 48 位的随机数,然后将其转换为 0 到 1 之间的双精度浮点数。由于使用固定的算法和参数,因此 Random 类生成的随机数序列是可预测的,但在统计上表现为随机分布,满足一般应用中对随机性的需求。

下面我们来验证一下java.util.Random是否是真正的等概率返回小数的

    static int times = 1000000;static double x = 0.7;static int count = 0;/*** 一次方:0~x上的小数出现的概率:验证Math.random()等概率返回小数*/private void once() {for (int i = 0; i < times; i++) {if (Math.random() < x) {count++;}}System.err.println((double) count / times);}

通过运行once方法会发现输出的值约等于0.7,也就说明确实是等概率

二、基于java.util.Random衍生的一些算法题

题一:要求实现0到x(x小于1)范围内,每个小数出现的概率是x的平方,比如x=0.7的时候,那么返回0.7的概率是0.49,也就是0.7的平方,代码实现如下:

    private void two() {for (int i = 0; i < times; i++) {if (Math.max(Math.random(), Math.random()) < x) {count++;}}System.err.println((double) count / times);}

运行结果就不在这里展示了,大家可以自行运行测试一下,为什么这个代码可以实现以x的平方概率返回x的值呢,我们知道max函数是返回两个数中较大的那一个,那么要满足Math.max(Math.random(), Math.random())返回的值小于x,那么就必须连续两次调用Math.random()返回的值都落在0到x的范围,比如当x=0.7的时候,两次Math.random()都返回0.5那么就满足条件,只要有一个返回大于0.7的值,那么max返回的值就会大于x,所以这个就实现了以x的平概率返回x的值

题二:以x的三次方概率返回x的值

这个题的思路跟题一的思路是一样的,也就是要同时满足三次Math.random()的值都落在0到x的范围内,那么只需要对其中一个Math.random()再取一次max即可,代码如下:

    /*** 一次方:0~1上的小数出现的概率为x的三次方*/private void three() {for (int i = 0; i < times; i++) {if (Math.max(Math.max(Math.random(), Math.random()), Math.random()) < x) {count++;}}System.err.println((double) count / times);}

题三:给你一个已经实现好的函数f,它可以等概率返回1到5之间的整数,也就是等概率返回1,2,3,4,5,要求利用f函数等概率返回1到7之间的整数,也就是等概率返回1,2,3,4,5,6,7,f函数的内容不能修改

我们知道Random是可以等概率返回0到1之间的小数的,那么如果能够实现等概率返回0到6之间的小数,那么要等概率返回1到7之间的整数就很简单了,首先第一步,我们来实现f函数的功能:

   /*** 等概率返回1到5之间的整数*/private static int f() {return (int) (Math.random() * 5) + 1;}

如果能直接使用random的话就很简单了,但是现在要求只能使用f函数,于是我们可以换一种思路,能不能通过f函数来等概率返回0和1呢,如果能的话,那么我们就可以等概率返回一个三个二进制位表示的整数,也就是0到7的范围(000~111)

f函数是等概率返回12345的,要想等概率返回0和1,我们可以把1和2归纳为一组,表示0,4和5归纳为一组,表示1,多出来的这个3就不能用了,就要强制重新通过f返回一个新的数据,直到不等于3,这样就相当于是把3的那20%的概率强制平均到1245上了,实现代码如下:
 

    /*** 利用f函数等概率返回0和1*/private int eqauls0And1() {int tmp = f1To5();while (tmp == 3) {tmp = f1To5();}return tmp < 3 ? 0 : 1;}

我们用eqauls0And1来获取一个有三个二进制位标示的整数,也就是下面的代码:

(eqauls0And1() << 2) + (eqauls0And1() << 1) + (eqauls0And1() << 0)

这个就可以等概率返回000~111之间的整数,但是我们的目标是0到6,多了一个7(也可以说0是多余的),思路同前面一样,遇到0或者是7的时候强制重来,下面以7为例:

    /*** 利用eqauls0And1从000到111等概率返回,也就是0到7等概率返回*/private int OOO_to_111() {int tmp = (eqauls0And1() << 2) + (eqauls0And1() << 1) + (eqauls0And1() << 0);return tmp;}/*** 类似eqauls0And1的思想等概率返回0到6的整数*/private int O_to_6() {int tmp = OOO_to_111();while (tmp == 7) {tmp = OOO_to_111();}return tmp;}

最后只需要将O_to_6返回的结果加1就能达到目的了,如果是把0当做多余的那个,那么就不用加1了,代码就不在这里展示了,只需要把O_to_6中7改成0即可

题四:已知一个函数f是不等概率返回0和1,要求里用f函数实现等概率返回0和1,同样不能修改f函数的逻辑

假设f函数返回0的概率是0.7,返回1的概率是0.3,那么f函数逻辑如下

    /*** 不等概率返回0和1*/private int notEquals0And1() {return Math.random() < 0.7 ? 0 : 1;}

解题思路跟前面一样,就是想办法把不等概率通过强制重做转换为等概率,用一个二进制位肯定是做不到了,于是我们可以想到用两个二进制位,那么返回两个二进制位的概率如下:

000.7*0.7=0.49
010.7*0.3=0.21
100.7*0.3=0.21
110.3*0.3=0.09

我们可以看到返回01和10的概率是相等的,那么只需要把返回00和11的时候强制重来,就可以了,代码如下:

第一种:连续重做两次

    private int equals0And1() {int a = notEquals0And1();int b = notEquals0And1();while (a == b) {a = notEquals0And1();b = notEquals0And1();}return a == 1 ? 0 : 1;}

第二种,像前面那种利用二进制来实现:

 (notEquals0And1() << 1) + (notEquals0And1() << 0)

如果这个返回的数是0或者3就强制重做,其实效果同上面是一样的:

    private int equals0And1V2() {int a = (notEquals0And1() << 1) + (notEquals0And1() << 0);while (a == 0 || a == 3) {a = (notEquals0And1() << 1) + (notEquals0And1() << 0);}return a == 1 ? 0 : 1;}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

类与对象(三)--static成员、友元

文章目录 1.static成员1.1概念&#x1f3a7;面试题✒️1.2static的特性&#x1f3a7;1.3思考&#x1f3a7; 2.友元2.1什么是友元&#xff1f;&#x1f3a7;2.2两种友元关系&#xff1a;&#x1f3a7; 1.static成员 1.1概念&#x1f3a7; &#x1f50e; static关键字用于声明类…

CSP-J 2021 T1 分糖果

文章目录 题目传送门算法解析总代码提交记录尾声 题目传送门 洛谷 P7909 [CSP-J 2021] 分糖果 算法解析 首先简化一下题目&#xff1a;取一个值 k k k&#xff0c;使 L ≤ k ≤ R L \leq k \leq R L≤k≤R&#xff0c;输出最大的 k % n k \% n k%n 一个数 % n \% n %n…

Selenium常见元素操作,学完就能上手

web端自动化测试在回归测试、兼容测试扮演着举足轻重的角色&#xff0c;作为web自动化测试工程师&#xff0c;日常工作主要的部分就是编写自动化测试用例代码&#xff0c;借助的开源框架来说&#xff0c;目前市场占有率较高的仍然是selenium。 如何使用selenium完成web页面元素…

测试用例的一些设计好方法,学会就是赚到!

一、概述 测试用例设计方法是指根据测试目的和测试对象&#xff0c;选择合适的技术和策略&#xff0c;来生成测试用例的方法。 测试用例是指导测试过程的重要文档&#xff0c;主要包括用例编号、测试目的、测试步骤、预期结果等。测试用例的编写需要遵循一些原则&#xff0c;…

Deepl翻译相关介绍

DeepL是一种机器翻译软件&#xff0c;它在2017年首次发布。该软件利用了神经网络和深度学习技术&#xff0c;以提供更准确和自然的翻译结果。DeepL的翻译质量被广泛认为是当前机器翻译技术中最佳的之一。 官网&#xff1a;DeepL翻译&#xff1a;全世界最准确的翻译 DeepL具有许…

离散数学——特殊图思维导图

离散数学——特殊图思维导图 目录 前言 内容 大纲 参考 前言 这是当初学习离散数学时整理的笔记大纲&#xff0c;其中包含了自己对于一些知识点的体悟。现将其放在这里作为备份&#xff0c;也希望能够对你有所帮助。 当初记录这些笔记只是为了在复习时更快地找到对应的知…

自研cloud框架专题–web模块(三)

项目特点一:框架集成 1.引入核心依赖2.配置相关功能 二:功能介绍 1.swagger支持并提供swagger快速配置2.knife增强swagger支持3.全局请求参数校验(Validation)支持4.字段脱敏支持5.默认jackson序列化6.xss,cors支持7.访问日志支持8.全局异常处理,统一返回结果9.系统关键及常用信…

程序运行的基本流程

操作系统&#xff08;应用程序&#xff09;&#xff1a; 装系统就是将操作系统安装到硬盘1中 计算机启动的基本过程&#xff1a; 总结&#xff1a; 程序一般保存在硬盘中&#xff0c;软件安装的过程就是将程序写入硬盘的过程程序在运行时会加载进入内存&#xff0c;然后由CPU…

【Docker】若依ruoyi项目部署

一 搭建局域网 1 # 搭建net-ry局域网&#xff0c;用于部署若依项目docker network create net-ry --subnet172.68.0.0/16 --gateway172.68.0.1 # 注意1&#xff1a;关闭宿主机的防火墙&#xff0c;否者容器内部的MySQL、redis等服务&#xff0c;外部访问不了&#xff1b;开放…

VS配置开发与远程调试笔记

先简单写一下&#xff0c;后续详细补充 场景&#xff1a;本地机器开发&#xff0c;虚拟机调试 准备工作&#xff1a; 由于要将生成的文件生成在虚拟机&#xff0c;避免反复拷贝&#xff0c;直接配置虚拟机共享文件夹进行写入&#xff0c;步骤如下&#xff1a; 虚拟机打开网…

董宇辉所有商标已转到与辉同行名下!

近日董宇辉此前由东方优选申请的所有商标已转到与辉同行主体名下&#xff0c;普推知产老杨经检索发现&#xff0c;这些商标都是2022年6月由东方优选提交申请&#xff0c;在2023年12月28时提交商标转让&#xff0c;最近转让成功&#xff0c;转让周期是2个半月左右。 转让的商标除…

uniapp在页面中中获取pages.json下pages设置navigationBarTitleText这个值?uniapp获取页面标题

一、问题描述 有个需求就是,在app.vue页面中首先会隐藏所有页面的title,然后在相应的页面会判断当前环境是否是在微信浏览器内&#xff0c;如果不是&#xff0c;则还原标题。 二、解决方法 在 pages.json 文件中设置 navigationBarTitleText&#xff0c;例如&#xff1a; {&qu…