洗牌算法!
问题:在斗地主小游戏,怎样保证发到玩家手中的牌具有等概率随机性。
问题抽象:1、给定一组排列,输出该排列的一个随机组合. 2、在n个不同的数中随机取出不重复的m个数.
一、Fisher-Yates Shuffle算法
1、算法简介: Fisher–Yates shuffle (高纳德)算法用来将一个有限集合生成一个随机排列的算法(数组随机排序)算法生成的随机排列是等概率的。由 Richard Durstenfeld 在1964年 描述,Donald E. Knuth 在 《The Art of Computer Programming》 中推广。 2、基本思路: 从原始数组中随机取一个之前没取过的数字到新的数组中。 ** 3、算法步骤:** 1. 初始化原始数组和新数组,原始数组长度为n(已知); 2. 从还没处理的数中(还剩k个) ,随机产生一个[0, k)之间的数字p(假设数组从0开始); 3. 从剩下的k个数中把第p个数取出; 4. 重复步骤2和3直到数字全部取完; 5. 从步骤3取出的数字序列便是一个打乱了的数列。 ** 4、算法效率:** 时间复杂度为O(n*n),空间复杂度为O(n)。 5、缺点: 算法效率不高且原始数组被修改了
二、Knuth-Durstenfeld Shuffle算法
1、算法简介: 该算法由Knuth 和Durstenfeld 在Fisher 等人的基础上对算法改进而来。 2、基本思路: 每次从未处理的数据中随机取出一个数字,然后把该数字放在数组的尾部。 ** 3、步骤:** 1. 建立一个数组大小为 n 的数组 A,分别存放 1 到 n 的数值 2. 生成一个从 0 到 n - 1 的随机数 xi; 3. 输出 A下标为 xi 的数值,即为第i个随机数; 4. 将 A 的第n-i个元素与第 xi 的元素互换; 如上,直到输出 m 个数为止 4、算法效率: 时间复杂度为O(n),空间复杂度为O(1) 5、缺点: 1.原始数组被修改了 2.无法处理不知道长度或动态增长的数组。
三、蓄水池抽样算法
1、算法简介: 针对从一个序列中随机抽取不重复的k个数,保证每个数被抽取到的概率为k/n 2、基本思路: 首先构建一个可放k个元素的蓄水池,将序列的前k个元素放入蓄水池中。然后从第k+1个元素开始,以k/n的概率来决定该元素是否被替换到池子中。
** 3、算法步骤:** 1. 将前k个数,依次放入蓄水池; 2. 接收第i个数据(i >k),在[0, i]范围内取以随机数x; 3. 判断: x<=k:用第i个数据替换蓄水池中的第x个数据。 x>k:步骤(4) 4. i=i+1并重复(2)(3)直到i=n
4、蓄水池中元素概率计算: 1. 对元素i(i<k),在前k步被选中的概率是1,从第k+1步开始,i不被选中的概率为k/k+1,那么读到第n个数时,i被选中的概率:1 * k/k+1 * k+1/ …n-1/n = k/n . 2. 对元素j(j>=k), 在j出现时被选中的概率为:k/j, 之后j不被换走的概率为j/j+1,i被选中的概率: k/j * j /j+1…n-1/n = k/n 5、算法效率 时间复杂度为O(n),空间复杂度为O(1) 6、优点: 1.原始数组不会被修改 2.可以不知道长度或动态增长的数组。