洗牌就是将已经排序好的数列打乱顺序
第一种方法
基本想法就是给每个元素标注一个随机的小数,然后再对序列进行排序。但是这种方法的开销比排序算法还要高。
目标
寻找一个线性复杂度的洗牌算法。
Knuth排序
将原始序列分成A B两个序列,A序列表示已经乱序,B序列表示尚未乱序。
步骤
-
将B序列的第一个元素取出,加入到A序列的末尾
-
从A序列中随机取出一个元素,与末尾的元素互换
-
循环直到B序列中没有元素为止
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import
java.util.Random;
public
class
Shuffle {
public
static
void
shuffle(Object[] li) {
Random r =
new
Random();
for
(
int
i=
0
;i<li.length;i++){
int
b=r.nextInt(i+
1
);
// 注意这里使用nextInt(i+1),而不是nextInt(i)或nextInt(n+1)
Object temp = li[b];
li[b] = li[i];
li[i] = temp;
}
}
}
|
洗牌故事
某在线纸牌游戏的洗牌算法是这样的:
for
i :=
1
to
52
do
begin
r := random(
51
) +
1
;
swap := card[r];
card[r] := card[i];
card[i] := swap;
end
;
这里有多个BUG
-
随机数不会随到最后一张卡片
-
乱序不是均匀的,取随机数应当是random(i),而不是random(51)
-
random()使用2^32的整数作为种子,因此有2^32种洗牌结果
-
seed=从0点到现在的毫秒数,因此只有8640万种洗牌结果
根据这些BUG,攻击者只要知道5张牌的顺序,时间与服务器同步,就可以实时计算出后续所有的卡片了。
最好的洗牌方法
-
有煽值高的随机数源,比如随机数硬件
-
经常检查随机数是否符合统计学规律,因为随机数硬件坏了之后是完全没有症状的
-
采用均匀的洗牌算法
最后,要说明一点洗牌算法不是那么简单的。