![d41b80a9e83743b8c89194a0914ab7c5.png](https://i-blog.csdnimg.cn/blog_migrate/851c1f8acb1a2f84a6dc0e4c61a69911.png)
写这篇文章的主要原因,是自己做了一个小抽奖。
![3366e9ad2684238476cfa9da37ed1356.png](https://i-blog.csdnimg.cn/blog_migrate/08d58e9b3092b8329d3dbcb25fdc9aae.jpeg)
备注: 参与抽奖需要转发的是下面这个想法。
不是文章,不是文章,不是文章
转发本想法 + 关注,送三箱可乐给3位幸运…www.zhihu.com之前也参与过其他人的抽奖,但是开奖过程和抽奖真实性,总是不那么权威和公开透明。
其实现场的抽奖算法很多,也很简便的方法。
这篇文章,并不是发表什么抽奖算法的高论,只是自己想拿来玩一玩,做个小游戏小抽奖。
所以设计一个大家都能自己参与运算,自己动手算出来结果的抽奖,来娱乐玩玩而已。
奖到底应该怎么抽?什么才是公正的抽奖算法?
抽奖绝大多数的情况下,一定会用到至少一个随机数。
随机数的生成,是会绝对影响到抽奖的公平公正的。
假设说100人转发,如果只是单纯抽一个1-100的随机数,这个随机数完全可以通过作弊的手段生成,而且生成也没有对证。
所以第一点,随机数很重要,必须要选取一个事先无法预测,但是事后大家都认可,不存在操控的随机数。
第二点,抽奖算法很重要,必须要在数学概率上,满足对中奖范围的离散定义域均匀分布。
随机数的生成 --- 数字种子和简单的加减运算
这个随机数,通过3个种子进行运算生成。
- 种子数 S1 为 上证指数 收盘数值,扔掉小数,直接取整数
- 种子数 S2 为 深证成指 收盘数值,扔掉小数,直接取整数
- 种子数 S3 为 创业板指 收盘数值,扔掉小数,直接取整数
这些种子数,都是时刻可能变化,并且所有人都能参与控制的数字。
当然,极端情况如果某人能控制股市,也可以作弊的。
都能控制股市了,还至于来这作弊么?
但种子数只是一部分而已,接下来是随机数的生成运算。
运算就简单一点了,只用加减法。
定义运算符 O ∈ {+,-}, 定义 O(0) 为 +,O(1) 为 -
有3个种子数,中间可以插两个运算符。
比如 S1 + S2 + S3 , 这就是插了两个运算符 {+,+},换成O就是 { O(0), O(0) }。
S1 + S2 - S3 , 这就是插了两个运算符 {+,-}, 换成O就是{ O(0), O(1) }。
取运算结果为 R ,
那么 R = S1 O(x) S2 O(y) S3, 其中 x ∈{0,1}, y ∈{0,1}
如果看到这,看懂了为什么我要取0,1函数的话,应该能猜得到下面就是01真值表了。
为什么要取3个种子,是为了获取2个运算符位.
2个符位就是 2^2 个结果,而我的抽奖,需要抽4个值出来,所以需要4个随机数。
![97e46368ad0cb497c5e9d8d24eae8db8.png](https://i-blog.csdnimg.cn/blog_migrate/e3a2dfbd9d6c2dabba5139512a7238d7.png)
从上到下,得到四个运算结果。
为了消除负数对后续运算的影响,分别取绝对值,得到结果为 R1, R2, R3, R4
抽奖算法的选取 --- 简单的取余运算
四个随机数已经有了,假设有 N 个人参与了转发。
那么现在就要通过一个运算,得到一个结果,来指定到底是哪一个人中奖。
也就是要找到一个中奖的编号。
先对这 N 个人编号,每个参与人的编号为 I , 按照转发的时间顺序,最早转发为编号 I = 0,逐个递增,最晚转发则为编号 I=(N-1).
随机数 R,通过取余运算得到编号,满足对中奖范围 {0,1,2,3...,N-1} 离散定义域的均匀分布。
举例说明,假设R为 123,N为 12 , 那么 R%N = 3.
假设R为 100,N为 10 , 那么 R%N = 0.
假设R为 109,N为 10 , 那么 R%N = 9.
现在有四个随机数 R1, R2, R3, R4,会分别各自做四次取余,得到四个编号 I1, I2, I3, I4
因为取余运算是各自独立事件,有可能会取余得到相同结果。
如果出现相同结果编号,则编号向后顺延加1,直至结果集中没有重复元素。
伪代码如下:
input:
Random number set R = {R1,R2,R3,R4}
Total number of people N
output:
The index set I
for each r in R {
i = r % N
while( I contain i ){
i = i + 1
}
set I add the element i
}
return I
举例:R = {100, 123, 53, 23}, N = 10
I1 = 100%10 = 0, 第0编号中奖
I2 =123%10 = 3,第3编号中奖
I3 = 53%10 = 3,由于3已经存在,所以顺延+1,第4编号中奖
I4 = 23%10 = 3,由于3和4都存在,顺延+1至第5,第5编号中奖
我的抽奖结果是3位幸运用户抽中可乐,1位幸运用户抽中充电宝。
所以要简单点,前三位中奖为可乐,第四位中奖为充电宝。
抽奖算法拓展
其实这是一个拓展性很强的算法,下面讲点抽象的数学内容,有兴趣了解的可以往下看。
这个抽奖流程需要的东西有:
- 种子数集合 S ,种子数目为m
- 数的二元运算集合 O
- 抽奖人数 N
- 能将随机数映射到离散域 {0,1,2,3...,N-1} 并且 均匀分布的算子 f
根据期望的中奖人数 E 可以调整 S 集合的大小,也就是调节m。
满足 2^(m-1) ≥ E 即可。
全世界的证券指数多得是,S应该很够用。
二元运算符用加减,只是为了方便对应01真值表,当然也可以换成其他满足随机分布的二元运算。如果换运算符,则要看运算集合 O 的 大小,应当使其满足
|O| ^(m-1) ≥ E
算子 f 也可以不用取余,但是取余是一个比较简单方便的方法。
根据自己的需要,f 完全可以换成其他的哈希函数,调整使其满足均匀分布即可。
抽奖代码如下:
import java.util.*;
public class Lottery {
public static final int SSEC = 3385; // 上证指数
public static final int SZI = 13666; // 深证成指
public static final int CHPI = 2684; // 创业板指
public static final int N = 76; // 参与抽奖的总人数 编号为0-75
public static void main(String[] args) {
List<Integer> rands = getRandList();
System.out.println("rands:" + rands.toString());
List<Integer> luckyIndexs = new ArrayList<Integer>();
rands.forEach(r -> {
int index = r % N;
while(luckyIndexs.contains(index)){
index++;
}
luckyIndexs.add(index);
});
System.out.println("luckyIndexs:" + luckyIndexs.toString());
}
public static List<Integer> getRandList() {
List<Integer> seeds = Arrays.asList(SSEC, SZI, CHPI);
return Arrays.asList(
seeds.get(0) + seeds.get(1) + seeds.get(2),
Math.abs(seeds.get(0) + seeds.get(1) - seeds.get(2)),
Math.abs(seeds.get(0) - seeds.get(1) + seeds.get(2)),
Math.abs(seeds.get(0) - seeds.get(1) - seeds.get(2))
);
}
}