在多个指定区间内生成随机数,且随机数总和固定算法
在多个指定区间内生成随机数,且随机数总和固定算法
一、介绍
最近项目上遇到了一个需求就是在多个指定区间内生成总和恒定的随机数。
示例:在[1-3]、[4-20]、[24-100]区间上分别生成一个随机数且要求随机数总和为40。
输出:随机数 a = 2 、b = 5 、c = 33.
想了一整天,最后用一种不是太完美的方法解决了这个问题。
二、思路简述
在多个指定区间生成随机数这个好弄,但是要求总和恒定就有点难办了。
下边的思路我会用用上面的示例来解释。
首先我们可以确定的是这个随机数的总和肯定是要>=1+4+24、<= 3+20+100的。即 >=所有区间最小值的和,<=所有区间最大值的和。
如果我们生成了一个随机数,我们用 总值 - 生成的数 = 剩下的总值 那么 剩下的总值 也必然满足 >=剩下区间的最小值总和,<=剩下区间最大值总和。
例如如我们生成 a=2 ,那么剩下总和为38,那么判断 38 是否满足 >=27 && <=123,如果满足则这个数符合规矩,我们只要在每次生成随机数后进行判断是否满足这个条件就可以了,如果不满足就继续生成。
如果前面的随机数都满足条件,那么最后一个数也必然满足条件。我们不用判断最后一个数,直接用 最后剩下的总和 即可。
问题
用我上面的思路去做必然会生成一组符合条件的随机数,但会出现一个问题。经过大量数据测试你会发现,我们设定的总和离 边界值 越近,生成的时间越长。这是为什么呢?
这是因为在我们的算法中,随机数每次都是在我们规定的区间内生成数据,前边生成的数据会对后边的值产生一定影响。如果前面生成的数过大,可能后边的数就要适当小一些才能满足要求。
当我们设定的总和离边界值远时,这时随机数可取的范围比较大,比如说此时我们设定随机数的总和为75,也就是随机数可取值的中位数。此时离边界是最远的,那么无论前边取何值,对后边的影响都会较小。一组随机数更可能达到要求。
但如果设定值正好为27或123那么此时随机数的取值已经是固定的。我们可以简单计算下 如果我们设置边界值为27那么随机数总和为27的概率就为 1/3 * 1/16 * 1/76
这还是只取三个随机数,如果我们要求的是10个、100个呢。那概率可就够小了。
解决方案
我的改进办法就是在每次判断后将随机数生成的范围缩小。
例如我第一次生成了5,第二次生成了20,我发现剩余总值为15,此时我们明白我们生成的数过大了,我们可以为15为第二个随机数的最高值来生成随机数,即可。即第二次在4-15上生成。这样的就会大大减少随机数生成的次数,从而减少算法消耗时间。
3. 代码展示
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;/*** @author lixiangxiang* @description 随机数算法* @date 2021/7/14 14:10*/
public class RandomAlgorithm {public static void main(String[] args) {List<PveScoreRule> rules = new ArrayList<>();int minTotal = 0;int maxTotal = 0;int total = 0;//生成分数范围for (int i = 0; i < 10; i++) {//生成最大边界int max = new Random().nextInt(109) % (109+1);//生成最小边界int min = new Random().nextInt(109) % (109+1);//如果min>max交换两者顺序if (max < min) {int temp = min;min = max;max = temp;}//得出随机数总和最高范围maxTotal += max;//得出随机数总和最低范围minTotal += min;rules.add(new PveScoreRule(max,min));}total = new Random().nextInt(maxTotal) % (maxTotal - minTotal + 1) + minTotal;//打印并验证随机数是否合法int num = 10;List<PveScoreRule> errRule = new ArrayList<>();int[] errArr = new int[10];//验证数据是否合法for (int i = 0; i < 10000; i++) {boolean flag = true;int sum = 0;int[] scores = createPveScore(rules,minTotal,maxTotal,total);for (int j = 0; j < scores.length; j++) {if (rules.get(j).max < scores[j]||rules.get(j).min>scores[j]) {flag = false;errRule = rules;errArr = scores;}System.out.print(scores[j]+"-");sum += scores[j];}}System.out.println("-"+flag)System.out.println("======"+Arrays.toString(errRule.toArray()));}/*** description: 创建随机分数** @author: lixiangxiang* @param rules 每个分数的范围* @param minTotal 总分最小下限* @param maxTotal 总分最大上限* @param total 总分* @return int[]* @date 2021/7/14 22:18*/public static int[] createPveScore(List<PveScoreRule> rules,int minTotal,int maxTotal,int total) {//存放随机数的数组int[] randoms = new int[rules.size()];for (int i = 0; i < rules.size(); i++) {//生成随机数 范围最小值到范围最大值int max = rules.get(i).max;int min = rules.get(i).min;//生成到最后一个时,直接将剩余的总数赋给他if(i == rules.size()-1){randoms[i] = total;return randoms;}int score = new Random().nextInt(max) % (max - min + 1) + min;//计算剩余的总数、最大边界和最小边界minTotal -= min;maxTotal -= max;int remandTotal = total - score;//判断生成的数是否有问题//1. 判断其剩余总数是否大于最大值 总数是否小于最小值如果有一个满足则进行循环,直到随机数可行时while(remandTotal > maxTotal || remandTotal < minTotal) {if (remandTotal > maxTotal) {//如果比之大说明随机数需要取更大值//通过改变随机数生成的下限,让其下限等于当前的随机数min = score;}//1. 判断其剩余总数是否小于最小值else {//如果比之大说明随机数需要取更小值//通过改变随机数生成的下限,让其上限等于当前的随机数max = score;}//重新生成随机分数score = new Random().nextInt(max) % (max - min + 1) + min;//重新计算剩余的总数remandTotal = total - score;}//如果上述条件都不成立,说明随机数合法存入到数组中randoms[i] = score;//计算真正剩余总值total -= score;}return randoms;}
}class PveScoreRule {int max;int min;public PveScoreRule(int max, int min) {this.max = max;this.min = min;}@Overridepublic String toString() {return "PveScoreRule{" +", max=" + max +", min=" + min +'}';}
}
2021/7/20更新
昨天知乎的一个大佬给了一个更好的思路,并指出我的算法中的不足。我顺着他的思路对这个算法做了一些改进。
1. 存在的问题
上面的算法虽然能够生成满足的范围条件的数,但其实他并不是随机的。因为前边的数一旦固定,后面数的范围会逐渐缩小,已经不是我们所规定的范围了。我之前的算法只能说是生成满足条件的数,但说不上随机。
2. 大佬的思路
要生成n个数,他们的和为sum。我们可以先生成最大的数n0,然后问题就变成生成n-1个数,他们的和为sum-n0。
比如按照例子[1,3]、[4,20]、[24,100],sum=40可以发现,最大的那个数实被和所限制范围为 [40-20-3,40-1-4]即[17,36],再与其本身的范围取交集,就变成[24,36],然后随机数得到,假设是33然后问题就变成了[1,3]、[4,20]中各生成一个数,他们的和为7这样。那么第二个数的范围就是[4,6],随机数取到5,那么最后一个数就是2。
但是这样做有一个问题,就是不那么随机,后续算的数字更容易落在区间的边缘处。
上面是大佬的思路,同时他也告诉我可以用dp遍历所有情况进行随机取出,但由于我的业务量比较大,需要一次生成一百多个随机数,这种方案肯定不行,
知乎大佬的回答
如何生成多个随机数,要求随机数在不同范围内,且总和一定? - 一丝混乱的回答 - 知乎
3. 新的思路
之前我们知道生成的数越往后,他的范围就会越小,值就会越确定,这样肯定会导致后面的数更接近区间的边缘值。
在这个思路的基础上,我想到将随机数生成的顺序改变,就是将随机数的下标打乱,记录下来,按照这个打乱的下标顺序生成随机数。等生成结束后再将随机数顺序还原。这样的话,由于第一个生成的数是不确定的,随机数的最后一个值不一定是最后一个生成的,那么生成的概率也将更随机。
当然这个思路只能是加大这组数的随机性,但仍是达不到完全随机。我经过大量数据的测试发现,这种方法生成的数仍是靠近区间边缘的数生成的概率越高,但相差的概率已经缩小很多。
目前我的水平可能也只能做到这一步了,如果有更好思路的大佬请在下方留言,感激不尽。
代码展示
package GenerateRandom;import java.io.Serializable;
import java.math.BigDecimal;
import java.util.*;/*** @author lixiangxiang* @description /* @date 2021/7/20 13:18*/
public class GenerateFloatScore {public static void main(String[] args) {List<FightPveScoreScopeDto> rules = generateRules();
// System.out.println(rules);float maxTotal = 0;float minTotal = 0;for (FightPveScoreScopeDto scope : rules) {maxTotal += scope.getMax();minTotal += scope.getMin();}float total = nextFloat(minTotal,maxTotal);List<Map<Float,Integer>> list = new ArrayList<>();for (int i = 0; i < rules.size(); i++) {list.add(new HashMap<>());}for (int i = 0; i < 1000000; i++) {float[] scores = generatePveScore(rules, total);for (int j = 0; j < scores.length; j++) {Map<Float,Integer> map = list.get(j);float score = scores[j];if(map.containsKey(score)) {//计算数值出现概率int probability = map.get(score)+1;map.put(score,probability);} else {map.put(score,1);}}
// judgeScores(floats, rules);}for (Map<Float,Integer> map : list) {System.out.println("第"+(list.indexOf(map)+1)+"个数范围为"+rules.get(list.indexOf(map)));for (Float key : map.keySet()) {System.out.println(key+"概率"+(map.get(key)/1000000f)+"---");}System.out.println();}}/*** description: 生成各个数范围** @author: lixiangxiang* @return java.util.List<GenerateRandom.FightPveScoreScopeDto>* @date 2021/7/20 15:37*/public static List<FightPveScoreScopeDto> generateRules() {List<FightPveScoreScopeDto> rules = new ArrayList<>();for (int i = 0; i < 10; i++) {//生成最大边界float max = nextFloat(0, 10.9f);//生成最小边界float min = nextFloat(0, 10.9f);
// System.out.println("交换前,max="+max+",min="+min);//如果min>max交换两者顺序if (max < min) {float temp = min;min = max;max = temp;}FightPveScoreScopeDto fightPveScoreScopeDto = new FightPveScoreScopeDto();fightPveScoreScopeDto.setMax(max);fightPveScoreScopeDto.setMin(min);rules.add(fightPveScoreScopeDto);}return rules;}/*** description:** @author: lixiangxiang* @param scores 生成的随机数* @param rules 每个数的范围* @return boolean* @date 2021/7/20 15:38*/public static boolean judgeScores(float[] scores,List<FightPveScoreScopeDto> rules) {List errRule = new ArrayList();float[] errArr = new float[10];boolean flag = true;int sum = 0;for (int j = 0; j < scores.length; j++) {if (rules.get(j).getMax() < scores[j]||rules.get(j).getMin()>scores[j]) {flag = false;errRule = rules;errArr = scores;}System.out.print(scores[j]+"-");sum += scores[j];}System.out.println("-"+flag);return flag;}/*** 生成max到min范围的浮点数* */private static float nextFloat( float min, float max) {BigDecimal cha = new BigDecimal(Math.random() * (max-min) + min);//保留 scale 位小数,并四舍五入return cha.setScale(1,BigDecimal.ROUND_HALF_UP).floatValue();}/*** description:** @author: lixiangxiang* @param scopes 每枪范围集合* @param total 总分* @return int[]* @date 2021/7/18 21:38*/public static float[] generatePveScore(List<FightPveScoreScopeDto> scopes,float total) {float maxTotal = 0;float minTotal = 0;for (FightPveScoreScopeDto scope : scopes) {maxTotal += scope.getMax();minTotal += scope.getMin();}//将scopes顺序打乱List<FightPveScoreScopeDto> shuffleScopes = new ArrayList<>();//生成打乱顺序后的下标int[] randomIndex = generateRandomArr(scopes.size());//按照生成的下标将集合打乱for (int i = 0; i < scopes.size(); i++) {shuffleScopes.add(scopes.get(randomIndex[i]));}//按照打乱的顺序生成分数float[] shuffleScores = generatePveScore(shuffleScopes, minTotal, maxTotal, total);float[] floatScores = new float[scopes.size()];//恢复生成分数顺序for (int i = 0; i < randomIndex.length; i++) {floatScores[randomIndex[i]] = shuffleScores[i];}return floatScores;}/*** description: 创建人机随机分数** @author: lixiangxiang* @param rules 所有枪数规则* @param minTotal 总分最小下限* @param maxTotal 总分最大上限* @param total 总分* @return int[]* @date 2021/7/14 22:18*/private static float[] generatePveScore(List<FightPveScoreScopeDto> rules, float minTotal, float maxTotal, float total) {//存放随机数的数组float[] res = new float[rules.size()];int length = rules.size();for (int i = length - 1; i >= 0; i--) {//从后往前生成//计算最后一个范围的真实范围 [总数-其他数范围最大值之和,总数-其他数范围最小值之和]与其原本范围的交集float max = rules.get(i).getMax();float min = rules.get(i).getMin();minTotal -= min;maxTotal -= max;//最后一个数不用值唯一,为剩下的total值float scopeMax = Math.min((total - minTotal),max);float scopeMin = Math.max((total - maxTotal),min);//生成随机分数//如果范围是0-0则不用算随机数,直接赋值if(scopeMax == 0 && scopeMin == 0) {res[i] = 0;continue;}//用计算出的真实范围生成随机数float score = nextFloat(scopeMin, scopeMax);res[i] = score;//重新对total赋值total -= score;}return res;}/*** description: 生成随机打乱顺序的下标** @author: lixiangxiang* @param size 、* @return int[]* @date 2021/7/20 15:35*/private static int[] generateRandomArr(Integer size) {int[] arr = new int[size];for (int i = 0; i < size; i++) {arr[i] = i;}int[] randomArr = new int[size];//索引int cur = 0;//位置int position = 0;int length = size;while (length > 0) {//生成随机位置position = new Random().nextInt(length);//将随机数生成位置的值记录到randomArr中randomArr[cur++] = arr[position];//将arr数组的最后一个值赋值给arr2中随机数所在的位置arr[position] = arr[length - 1];//数组长度-1length--;}return randomArr;}
}class FightPveScoreScopeDto implements Serializable {/*** 分数最大值*/private Float max;/*** 分数最小值*/private Float min;public Float getMax() {return max;}public void setMax(Float max) {this.max = max;}public Float getMin() {return min;}public void setMin(Float min) {this.min = min;}@Overridepublic String toString() {return "FightPveScoreScopeDto{" +"max=" + max +", min=" + min +'}';}
}
在多个指定区间内生成随机数,且随机数总和固定算法相关推荐
- Excel 技巧篇-公式实现在指定范围内生成指定小数位的随机数
Excel 生成随机数的公式有两种: 第一个是,生成 0 到 1 之间的小数 =RAND() 第二个是,生成指定两个数之间的整数 =RANDBETWEEN(1,100) 如果我们想要在指定范围内生成指 ...
- 组合数学-容斥原理-求指定区间内与n互素的数的个数
求指定区间内与n互素的数的个数 给出整数n和r.求区间[1,r]中与n互素的数的个数. 去解决它的逆问题,求不与n互素的数的个数. 考虑n的所有素因子pi(i=1···k) 在[1,r]中有多少数能被 ...
- Python实现正态分布指定区间内【置信区间】概率值计算
学过概率论的相信对于正态分布都不会陌生,这个可以说是非常经典非常重要的一种概率分布了,在现实生活中也是广泛在使用的,比如说:男女的升高服从正态分布,灯泡的寿命服从正态分布,某地区的降雨量服从正态分布, ...
- R语言使用seq函数生成数据序列、seq函数在指定范围内生成固定长度的序列、指定数据序列的第一个数值、最后一个数值以及另外一个数据序列(along.with)
R语言使用seq函数生成数据序列.seq函数在指定范围内生成固定长度的序列.指定数据序列的第一个数值.最后一个数值以及另外一个数据序列(along.with) 目录
- MySQL生成指定区间内的随机时间
UPDATE dw_vital SET create_time = DATE_ADD('2020-1-01 11:29:00', INTERVAL ROUND(RAND() * 730 + 1) DA ...
- mybatis 中针对指定区间内的时间的查询
https://blog.csdn.net/qq_38061755/article/details/79826532
- Redis 笔记(04)— list类型(作为消息队列使用、在列表头部添加元素、尾部删除元素、查看列表长度、遍历指定列表区间元素、获取指定区间列表元素、阻塞式获取列表元素)
Redis 的列表是链表而不是数组.这意味着 list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n). 当列表弹出了最后一个元素之后,该数据结构自动被删除, ...
- c语言学习进阶-C语言程序实现生成指定区间指定个数随机数
##C语言程序实现生成指定区间指定个数随机数 设计一个自动数据生成程序,能自动生成指定行数的随机整数并写入到一个文件当中,随机整数的范围可以被控制,例如控制在0 到100 间,这个程序的操作命令行参数 ...
- 【C++操作手册】C++生成指定范围内随机数rand(随机数种子)
c++中的rand函数是用来生成随机数的,它的生成范围是0-Random_max,这个一个内部定义的一个常量,如果我们需要每次生成的随机数不同,这时需要加上随机数种子,利用srand()函数,我们可以 ...
最新文章
- DevOps和容器:本地or云端,如何选择?
- js提取url参数的几种方法。(搜集)
- 网络营销中一旦网站改版需要遵循哪些网络营销原则呢?
- cmake (3)多个源文件aux_source_directory
- 华为防火墙查看日志命令_防火墙接入互联网方式,到底有哪些呢?5分钟学会防火墙入网...
- 第5章--高级数据管理
- Linux eBPF 程序构成与通信原理
- Linux学习笔记---更新软件源
- java中switch条件_关于java:你能在Android的switch-case中使用条件语句吗?
- JSONObject.fromObject爆红,显示无fromObject方法
- axure8.0注册码
- VC MFC 发送模仿键盘消息
- 伽卡他卡使用教程_【伽卡他卡电子教室教师端介绍】伽卡他卡电子教室教师端特色_伽卡他卡电子教室教师端说明-最笨下载...
- php配置北京时间,php如何设置北京时间_后端开发
- java计算机毕业设计在线辅导答疑系统源码+mysql数据库+系统+lw文档+部署
- 说企业自研应用是误区的,非蠢即坏
- Beego2 使用Session时SetCookie无效
- matlab中set的用法,set函数(set函数的使用方法)
- 【Android之OkHttp介绍】
- 如何开发自己的通用Mapper
热门文章
- 在OpenCV里实现内旋轮线
- Ubuntu使用gym保存视频报错“Unknown encoder ‘libx264‘”
- 搭建阿里云服务器,实现服务端与客户端socket数据通信(详细版)
- 未来5年光通信系统十大技术趋势发布
- 两周看完乔布斯传,说说感受
- Weight Normalization(WN) 权重归一化
- 计算机c盘属性不显示安全选项,Win10系统下磁盘属性没有安全选项卡怎么解决?...
- 激情个P—leo看赢在中国第三季(2)
- uedit如何连接本机linux虚拟机,实现文件交互
- 会员价值分析-波士顿矩阵