权重随机算法的java实现
一、概述
平时,经常会遇到权重随机算法,从不同权重的N个元素中随机选择一个,并使得总体选择结果是按照权重分布的。如广告投放、负载均衡等。
如有4个元素A、B、C、D,权重分别为1、2、3、4,随机结果中A:B:C:D的比例要为1:2:3:4。
总体思路:累加每个元素的权重A(1)-B(3)-C(6)-D(10),则4个元素的的权重管辖区间分别为[0,1)、[1,3)、[3,6)、[6,10)。然后随机出一个[0,10)之间的随机数。落在哪个区间,则该区间之后的元素即为按权重命中的元素。
实现方法:
利用TreeMap,则构造出的一个树为:
B(3)
/ \
/ \
A(1) D(10)
/
/
C(6)
然后,利用treemap.tailMap().firstKey()即可找到目标元素。
当然,也可以利用数组+二分查找来实现。
二、源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
package com.xxx.utils; import com.google.common.base.Preconditions; import org.apache.commons.math3.util.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.SortedMap; import java.util.TreeMap; public class WeightRandom<K,V extends Number> { private TreeMap<Double, K> weightMap = new TreeMap<Double, K>(); private static final Logger logger = LoggerFactory.getLogger(WeightRandom. class ); public WeightRandom(List<Pair<K, V>> list) { Preconditions.checkNotNull(list, "list can NOT be null!" ); for (Pair<K, V> pair : list) { double lastWeight = this .weightMap.size() == 0 ? 0 : this .weightMap.lastKey().doubleValue(); //统一转为double this .weightMap.put(pair.getValue().doubleValue() + lastWeight, pair.getKey()); //权重累加 } } public K random() { double randomWeight = this .weightMap.lastKey() * Math.random(); SortedMap<Double, K> tailMap = this .weightMap.tailMap(randomWeight, false ); return this .weightMap.get(tailMap.firstKey()); } } |
三、性能
4个元素A、B、C、D,其权重分别为1、2、3、4,运行1亿次,结果如下:
元素 | 命中次数 | 误差率 |
A | 10004296 | 0.0430% |
B | 19991132 | 0.0443% |
C | 30000882 | 0.0029% |
D | 40003690 | 0.0092% |
从结果,可以看出,准确率在99.95%以上。
现在app就是雨后春笋,嗖嗖的往外冒啊,有经验的、没经验的、有资历的、没资历的都想着创业,创业的90%以上都要做一个app出来,好像成了创业的标配。
做了app就得推广啊,怎么推,发券送钱是最多用的被不可少的了,现在好多产品或者运营都要求能够随机出优惠券的金额,但是呢又不能过于随机,送出去的券都是钱吗,投资人的钱,是吧。
所以,在随机生成的金额中就要求,小额度的几率要大,大额度的几率要小,比如说3元的70%,5块的25%,10块的5%,这个样子的概率去生成优惠券,这个怎么办呢?
对于上述的问题,直接用我们的Random.next(Integer range);就不够了。因为这个伪随机不带权重,3,5,10出现的概率都是一样的。
实现思路
还是拿上述的例子,3出现的概率是70%,我们给他的权重赋值为70,5出现的概率为25%,我们给他的权重赋值为25,10出现的概率为5%,我们给他的权重赋值为5.
我们按照顺序计算出权重的加和,把当前数字出现的权重加和前的值作为其权重范围的起点值,把加和后的值作为其权重范围的终点值。
这样的话,我们就可以使用Random.next(100)来做随机数,然后判断随机数落在的范围,然后映射到对应的优惠券数值即可。
java实现
package com.nggirl.test.weight.random;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
public class WeightRandom {
public static void main(String[] args){
WeightRandom wr = new WeightRandom();
wr.initWeight(new String[]{"1","2","3","4"}, new Integer[]{100,100,200,600});
Random r = new Random();
for(int i = 0; i < 10; i++){
Integer rv = r.nextInt(wr.getMaxRandomValue());
System.out.println(rv);
System.out.println(wr.getElementByRandomValue(rv).getKey() + " " + rv);
}
HashMap<String, Integer> keyCount = new HashMap<String, Integer>();
keyCount.put("1", 0);
keyCount.put("2", 0);
keyCount.put("3", 0);
keyCount.put("4", 0);
for(int i = 0; i < 10000; i++){
Integer rv = r.nextInt(wr.getMaxRandomValue());
String key = wr.getElementByRandomValue(rv).getKey();
keyCount.put(key, keyCount.get(key).intValue()+1);
}
System.out.println("");
}
private List<WeightElement> weightElements;
public void initWeight(String[] keys, Integer[] weights){
if(keys == null || weights == null || keys.length != weights.length){
return;
}
weightElements = new ArrayList<WeightElement>();
for(int i=0; i< keys.length; i++){
weightElements.add(new WeightElement(keys[i], weights[i]));
}
rangeWeightElemnts();
printRvs();
}
private void rangeWeightElemnts(){
if(weightElements.size() == 0){
return;
}
WeightElement ele0 = weightElements.get(0);
ele0.setThresholdLow(0);
ele0.setThresholdHigh(ele0.getWeight());
for(int i = 1; i < weightElements.size(); i++){
WeightElement curElement = weightElements.get(i);
WeightElement preElement = weightElements.get(i - 1);
curElement.setThresholdLow(preElement.getThresholdHigh());
curElement.setThresholdHigh(curElement.getThresholdLow() + curElement.getWeight());
}
}
public WeightElement getElementByRandomValue(Integer rv){
//因为元素权重范围有序递增,所以这里可以改为二分查找
for(WeightElement e:weightElements){
if(rv >= e.getThresholdLow() && rv < e.getThresholdHigh()){
return e;
}
}
return null;
}
public Integer getMaxRandomValue(){
if(weightElements == null || weightElements.size() == 0){
return null;
}
return weightElements.get(weightElements.size() - 1).getThresholdHigh();
}
public void printRvs(){
for(WeightElement e:weightElements){
System.out.println(e.toString());
}
}
static class WeightElement{
/**
* 元素标记
*/
private String key;
/**
* 元素权重
*/
private Integer weight;
/**
* 权重对应随机数范围低线
*/
private Integer thresholdLow;
/**
* 权重对应随机数范围高线
*/
private Integer thresholdHigh;
public WeightElement(){
}
public WeightElement(Integer weight){
this.key = weight.toString();
this.weight = weight;
}
public WeightElement(String key, Integer weight){
this.key = key;
this.weight = weight;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
public Integer getThresholdLow() {
return thresholdLow;
}
public void setThresholdLow(Integer thresholdLow) {
this.thresholdLow = thresholdLow;
}
public Integer getThresholdHigh() {
return thresholdHigh;
}
public void setThresholdHigh(Integer thresholdHigh) {
this.thresholdHigh = thresholdHigh;
}
public String toString(){
return "key:"+this.key + " weight:" + this.weight + " low:"+this.thresholdLow+" heigh:"+this.thresholdHigh;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
二分法的实现
public WeightElement getElementByRandomValue(Integer rv){
if(rv < 0 || rv > getMaxRandomValue()-1){
return null;
}
//此时rv必然在0 - getMaxRandomValue()-1范围内,
//也就是必然能够命中某一个值
int start = 0, end = weightElements.size() - 1;
int index = weightElements.size()/2;
while(true){
if(rv < weightElements.get(index).getThresholdLow()){
end = index - 1;
}else if(rv >= weightElements.get(index).getThresholdHigh()){
start = index + 1;
}else{
return weightElements.get(index);
}
index = (start + end)/2;
}
}
基本算法描述如下:
1、每个广告增加权重
2、将所有匹配广告的权重相加sum,
3、以相加结果为随机数的种子,生成1~sum之间的随机数rd
4、.接着遍历所有广告,访问顺序可以随意.将当前节点的权重值加上前面访问的各节点权重值得curWt,判断curWt >= rd,如果条件成立则返回当前节点,如果不是则继续累加下一节点. 直到符合上面的条件,由于rd<=sum 因此一定存在curWt>=rd。
特别说明:
此算法和广告的顺序无关
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; public class Test { /** * @param args */ @SuppressWarnings ( "unchecked" ) public static void main(String[] args) { List<Node> arrNodes = new ArrayList<Node>(); Node n = new Node( 10 , "测试1" ); arrNodes.add(n); n = new Node( 20 , "测试2" ); arrNodes.add(n); n = new Node( 30 , "测试3" ); arrNodes.add(n); n = new Node( 40 , "测试4" ); arrNodes.add(n); //Collections.sort(arrNodes, new Node()); Map<String, Integer> showMap = null ; int sum = getSum(arrNodes); int random = 0 ; Node kw = null ; for ( int k = 0 ; k < 20 ; k++) { showMap = new LinkedHashMap<String, Integer>(); for ( int i = 0 ; i < 100 ; i++) { random = getRandom(sum); kw = getKW(arrNodes, random); if (showMap.containsKey(kw.kw)) { showMap.put(kw.kw, showMap.get(kw.kw) + 1 ); } else { showMap.put(kw.kw, 1 ); } //System.out.println(i + " " +random + " " + getKW(arrNodes, random)); } System.out.print(k + " " ); System.out.println(showMap); } } public static Node getKW(List<Node> nodes, int rd) { Node ret = null ; int curWt = 0 ; for (Node n : nodes){ curWt += n.weight; if (curWt >= rd) { ret = n; break ; } } return ret; } public static int getSum(List<Node> nodes) { int sum = 0 ; for (Node n : nodes) sum += n.weight; return sum; } public static int getRandom( int seed) { return ( int )Math.round(Math.random() * seed); } } class Node implements Comparator{ int weight = 0 ; String kw = "" ; public Node() {} public Node( int wt, String kw) { this .weight = wt; this .kw = kw; } public String toString(){ StringBuilder sbBuilder = new StringBuilder(); sbBuilder.append( " weight=" ).append(weight); sbBuilder.append( " kw" ).append(kw); return sbBuilder.toString(); } public int compare(Object o1, Object o2) { Node n1 = (Node)o1; Node n2 = (Node)o2; if (n1.weight > n2.weight) return 1 ; else return 0 ; } } |
根据权重进行抽取的算法应用比较广泛,其中抽奖便是主要用途之一。正好这几天也正在进行抽奖模块的开发,整个抽奖模块涉及到的地方大概有三处,分别是后台进行奖品的添加(同时设置权重和数量),前台根据后台配置生成抽奖队列并根据指令开始抽奖活动,最后一部分是后台统计中奖情况并设置物流状态。本文主要针对前台抽奖算法进行介绍如何根据权重设置每个奖品被抽到的概率。
抽奖算法的核心是根据权重设置随机数出现的概率,在此我将它封装成一个生成随机数的随机类,代码如下:
- /**
- * JAVA 返回随机数,并根据概率、比率
- *
- */
- public class MathRandom {
- private static Log logger = LogFactory.getLog(MathRandom.class);
- /**
- * Math.random()产生一个double型的随机数,判断一下 每个奖品出现的概率
- *
- * @return int
- *
- */
- public int PercentageRandom(List<RewardPrize> prizes) {
- DecimalFormat df = new DecimalFormat("######0.00");
- int random = -2;
- try{
- double sumWeight = 0;
- //计算总权重
- for(RewardPrize rp_1 : prizes){
- sumWeight += rp_1.getPrize_weight();
- }
- double randomNumber;
- randomNumber = Math.random();
- System.out.println("randomNumber是:" + randomNumber);
- double d1 = 0;
- double d2 = 0;
- for(int i=0;i<prizes.size();i++){
- d2 += Double.parseDouble(String.valueOf(prizes.get(i).getPrize_weight()))/sumWeight;
- if(i==0){
- d1 = 0;
- }else{
- d1 +=Double.parseDouble(String.valueOf(prizes.get(i-1).getPrize_weight()))/sumWeight;
- }
- if(randomNumber >= d1 && randomNumber <= d2){
- random = i;
- System.out.println("d1是:" + d1);
- System.out.println("d2是:" + d2);
- break;
- }
- }
- }catch(Exception e){
- System.out.println(e.getMessage());
- logger.error("生成抽奖随机数出错,出错原因:" + e.getMessage());
- random = -1;
- }
- return random;
- }
- /**
- * 测试主程序
- *
- * @param agrs
- */
- public static void main(String[] agrs) {
- int i = 0;
- MathRandom a = new MathRandom();
- List<RewardPrize> prizes = new ArrayList();
- for(int m=0;m<100;m++){
- RewardPrize rp = new RewardPrize();
- rp.setPrize_amount(10);//每个奖品数量设置10个
- rp.setPrize_weight(1);//每个奖品的权重都设置成1,也就是每个奖品被抽到的概率相同(可根据情况自行设置权重)
- prizes.add(rp);
- }
- for (i = 0; i <= 100; i++)// 打印100个测试概率的准确性
- {
- System.out.println(a.PercentageRandom(prizes));
- }
- }
- }
简单介绍一下上面的代码含义,首先计算出待选奖品的总权重,这样做的目的是可以随意设置奖品权重,不必再考虑权重之和是否等于100。随机规则是首先生成一个随机数randomNumber(生成的随机数位于0到1的左开右闭区间),然后分别计算出当前奖品前前面所有有奖品(不包括当前奖品)的概率和d1和当前奖品后面(包括当前奖品)所有奖品的概率和d2,然后判断生成的随机数randomNumber是否已处于d1和d2之间,如果处于该区间之内则当前奖品将被抽中。
权重随机算法在抽奖,资源调度等系统中应用还是比较广泛的,一个简单的按照权重来随机的实现,权重为几个随机对象(分类)的命中的比例,权重设置越高命中越容易,之和可以不等于100;
简单实现代码如下:
import java.util.ArrayList;
import java.util.List;
import java.util.Random; public class WeightRandom {
static List<WeightCategory> categorys = new ArrayList<WeightCategory>();
private static Random random = new Random(); public static void initData() {
WeightCategory wc1 = new WeightCategory("A",60);
WeightCategory wc2 = new WeightCategory("B",20);
WeightCategory wc3 = new WeightCategory("C",20);
categorys.add(wc1);
categorys.add(wc2);
categorys.add(wc3);
} public static void main(String[] args) {
initData();
Integer weightSum = 0;
for (WeightCategory wc : categorys) {
weightSum += wc.getWeight();
} if (weightSum <= 0) {
System.err.println("Error: weightSum=" + weightSum.toString());
return;
}
Integer n = random.nextInt(weightSum); // n in [0, weightSum)
Integer m = 0;
for (WeightCategory wc : categorys) {
if (m <= n && n < m + wc.getWeight()) {
System.out.println("This Random Category is "+wc.getCategory());
break;
}
m += wc.getWeight();
} } } class WeightCategory {
private String category;
private Integer weight; public WeightCategory() {
super();
} public WeightCategory(String category, Integer weight) {
super();
this.setCategory(category);
this.setWeight(weight);
} public Integer getWeight() {
return weight;
} public void setWeight(Integer weight) {
this.weight = weight;
} public String getCategory() {
return category;
} public void setCategory(String category) {
this.category = category;
}
}
权重随机算法的java实现的更多相关文章
- java 权重随机算法实现
import java.util.*; /** * 权重随机算法实现 * a b c d 对应权重范围 --- [0,1).[1,3).[3,6).[6,10) */ public class Ran ...
- java实现权重随机算法
权重随机算法在抽奖,资源调度等系统中应用还是比较广泛的,一个简单的按照权重来随机的实现,权重为几个随机对象(分类)的命中的比例,权重设置越高命中越容易,之和可以不等于100: 简单实现代码如下: im ...
- 权重随机算法Java实现
权重随机算法在抽奖,资源调度等系统中应用还是比较广泛的,一个简单的按照权重来随机的实现,权重为几个随机对象(分类)的命中的比例,权重设置越高命中越容易,之和可以不等于100: 简单实现代码如下: ? ...
- Java实现 LeetCode 528 按权重随机选择(TreeMap)
528. 按权重随机选择 给定一个正整数数组 w ,其中 w[i] 代表位置 i 的权重,请写一个函数 pickIndex ,它可以随机地获取位置 i,选取位置 i 的概率与 w[i] 成正比. 说明 ...
- 几种简单的负载均衡算法及其Java代码实现
什么是负载均衡 负载均衡,英文名称为Load Balance,指由多台服务器以对称的方式组成一个服务器集合,每台服务器都具有等价的地位,都可以单独对外提供服务而无须其他服务器的辅助.通过某种负载分担技 ...
- 粒子群优化算法及其java实现
憋了两周终于把开题报告憋出来了,再一次证明自己不适合搞学术,哎--,花了点时间把报告中提到的粒子群算法看了看,看了些资料,用java跑起来. 算法简介 粒子群算法最先由Barnhart博士和Kenne ...
- hdu 4712 Hamming Distance ( 随机算法混过了 )
Hamming Distance Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 65535/65535 K (Java/Others) ...
- 一个基于RSA算法的Java数字签名例子
原文地址:一个基于RSA算法的Java数字签名例子 一.前言: 网络数据安全包括数据的本身的安全性.数据的完整性(防止篡改).数据来源的不可否认性等要素.对数据采用加密算法加密可以保证数据本身的安全性 ...
- python的random模块及加权随机算法的python实现
random是用于生成随机数的,我们可以利用它随机生成数字或者选择字符串. random.seed(x)改变随机数生成器的种子seed. 一般不必特别去设定seed,Python会自动选择seed. ...
随机推荐
- 如何判断JDK是32位还是64位
第一种方法 在CMD窗口中使用java -version 命令进行查看 如果是64位的则会显示 Java HotSpot<TM>64-Bit 字样,32位的则没有类似信息. 注:这是Sun ...
- openmpi+NFS+NIS搭建分布式计算集群
1. 配置防火墙 正确配置防火墙的过滤规则,否则会造成NFS文件系统的挂载失败,NIS账户认证的失败,mpirun远程任务实例投放的失败.一般情况下,计算集群是在内部局域网中使用,所以可 ...
- python之enumerate枚举 第二篇(六):enumerate枚举
[Python之旅]第二篇(六):enumerate枚举 python enumerate枚举 摘要: 1.普通情况下打印列表中索引号及其对应元素 使用下面的循环: 1 2 3 4 5 6 ...
- ASP.NET MVC 第四回 向View传值
一.ViewData与TempData属性来向View页传递对象 上文中已经提到,使用ViewData可以将数据由Controller传递到View 在前文中我们建立了EiceController类 ...
- datazen 修改instanceid db_encryption_key
切换到Datazen.Enterprise.Server.3.0.3327.exe 所在的目录 运行如下命令: Datazen.Enterprise.Server.3.0.3327.exe DATAZ ...
- 嵌入式css样式,写在当前的文件中
现在有一任务,把下面的“超酷的互联网”.“服务及时贴心”.“有趣易学”这三个短词文字字号修改为18px. 如果用内联式css样式的方法进行设置将是一件很头疼的事情(为每一个<span>标签 ...
- xcode中如何安装多个版本的模拟器
在xcode里面,安装的时间默认自带的有模拟器,有时间为了调试需要使用个多个版本的模拟器 在xcode -> preference 里面 选择download,这里你可下载你需要的模拟器
- Java设计模式(学习整理)----装饰模式
1.概念: (在我看来,模式就像是是一种思想,在这种思想的指引下,对代码和结构的一番加工和整合而已!都是套路!) 装饰模式又称包装(Wrapper)模式,是以对客户端透明的方式扩展对象的功能,是继承关 ...
- 【cogs858】磁性链
[题目描述] 有N块编号为1~N的特殊磁石相互吸附组成一条磁性链,只有它们紧挨着时才会传递吸力,他们之间的吸力很大,如果我们要从N块相连的磁石中取出一块,那么需要消耗N-1个单位的能量,空缺处不再有吸 ...
- 关于fork( )函数父子进程返回值的问题
fork()是linux的系统调用函数sys_fork()的提供给用户的接口函数,fork()函数会实现对中断int 0x80的调用过程并把调用结果返回给用户程序. fork()的函数定义是在init ...