洗牌算法在实际应用中使用的比较广泛,比如抽奖、三国杀游戏等等。由于要完全理解洗牌算法存在一定的难度,所以洗牌算法也经常被拿来做算法笔试题。例如以下两个常见的笔试题:在n个不同的数中随机取出不重复的m个数;打乱一副扑克牌,不能用额外空间,证明为什么是随机的?

在平时开发中,我们经常会用到随机数,而我们一般要生成随机数时,都是使用Random类实现。然而Random 类实现的随机数是真正的随机数吗?Java中的Random又是如何实现随机数的生成呢?

Random是通过一个种子通过一定的算法来获取的随机数,实现的算法有很多,在Java的Random类中的算法如下:

public int nextInt() {return next(32);
}protected int next(int bits) {long oldseed, nextseed;AtomicLong seed = this.seed;do {oldseed = seed.get();nextseed = (oldseed * multiplier + addend) & mask;} while (!seed.compareAndSet(oldseed, nextseed));return (int)(nextseed >>> (48 - bits));
}

如果种子一样,产生的随机数也是一样的,这种计算随机数不满足随机性,所以我们把Random生成的随机数通常称为伪随机数。通常随机数是无法预先计算出来的,并且每个数字出现的概率是一样的。随机数必须满足以下两个条件:

  • 不可计算性,即在随机数产生前,不能通过任何方式计算出来。
  • 机会均等性,即需要保证每个数出现的概率是相等的。

在平常生活中,通过掷骰子的方式就可以很容易获取一个随机数。但在计算机中,要生成一个随机数,却不是一件简单的事。首先,我们需要考虑在一定范围内如何随机

  • Fisher-Yates Shuffle洗牌算法

假设我们有一个数组a[1,2,3],我们需要实现一个算法,将顺序的1,2,3随机的打乱在数组中。而这里的随机是表示每个数最终落在每个位置的概率都需要是一样的。我们可以通过随机抽取数组,之后将抽取的数放入到另一个数组的位置的方式来实现随机打乱数组,例如,

  1. 新数组的第一个位置抽取1的概率为1/3(1/n),而抽取不到1的概率为2/3(n-1/n);
  2. 当第一个位置抽取到了1,第二个位置抽取2的概率为1/2(在第二步的概率,不是整个过程的概率),那么在整个过程中第二个位置抽取到数字2的概率是第一个位置抽取不到数字2的概率*第二个位置抽到到数字2的概率,即2/3*1/2=1/3,即(n-1/n)*(1/n-1)=1/n;
  3. 依次类推,在前面两个抽取为数字1,2时,在新数组第三个位置被抽到3的概率为1,而第一步和第二步抽取不到3的概率分别为2/3、1/2,所以整个过程中第三个位置抽取到数字3的概率为第一个位置抽取不到数字3的概率*第二个位置抽取不到数字3的概率*第三个位置抽到到数字3的概率,即2/3*1/2*1=1/3,即(n-1/n)*(1/n-1)*1=1/n。

依次类推,一个元素m被放入第i个位置的概率P = 前i-1个位置选择元素时没有选中m的概率 * 第i个位置选中m的概率,即

在 i = 0 的情况下,很显然p=1/n。对于 i > 0 的情况,前一个式子的分子正好能把下一个式子的分母约去,到最后也只有第一个式子分母还在。因此,不管是哪一轮摸到了哪一个数,概率都是1/n ,所以这个数组的每个排列组合都是等概率的。

以上这种类似抽牌来实现的洗牌算法,这个算法是由两个科学家 Fisher 和 Yates提出的,所以人们称之为Fisher-Yates Shuffle算法。我们可以通过Java来实现一份Fisher-Yates Shuffle算法打乱一份扑克牌的代码:

private Random rand = new Random();
private String[] b;
public static void shuffle(String[] a)
{int n = a.length;for (int i = 0; i < n; i++){  // Exchange a[i] with random element in a[i..N-1]int r = rand.nextInt(n-i);String temp = a[r];b[i] = temp;a.remove(temp);}
}

Fisher-Yates Shuffle算法的时间复杂度是O(n),空间复杂度是O(n)。

  • Knuth-Durstenfeld 洗牌算法

Fisher-Yates Shuffle算法的特点是通过随机抽取的方式来实现的,这种洗牌方式的缺点就是需要额外的空间来存储随机抽取的数字,那有没有一种算法可以省去额外的存储空间呢?

Knuth 和 Durstenfeld 在Fisher-Yates Shuffle算法研究的基础上对洗牌算法进行了改进,通过将随机抽取的数字与在原来数组上的数字进行交换,这样就省去了额外的空间,降低了空间复杂度。

private Random rand = new Random();
public static void shuffle(String[] a)
{int n = a.length;for (int i = 0; i < n; i++){  // Exchange a[i] with random element in a[i..N-1]int r = rand.nextInt(n-i);String temp = a[i];a[i] = a[r];a[r] = temp;}
}

这样洗牌算法的时间复杂度是O(n),而空间复杂度是O(1)。

c++随机打乱数组_【洗牌算法】你确定这样的抽奖算法是随机的?相关推荐

  1. c++随机打乱数组_编程之路之php数组操作详解【文末有惊喜】

    一.数组的操作 数组是一种复合数据类型,是数据的集合. 数组中的数据称为元素,每个元素是由键和值组成的键值对. 二.数组分类 1.关联数组 数据是由键和值形式构成的数组,键和值有一定关系 $arr = ...

  2. c++随机打乱数组_第四天 Java 数组与排序

    一. 数组 数组(Array),是多个相同类型数据按一定顺序排列 的集合,并使用一个名字命名,并通过编号的方式 对这些数据进行统一管理. 数组的常见概念 数组名 下标(或索引) 元素 数组的长度 数组 ...

  3. Algorithm:C+语言实现之数组相关算法(和为定值的两个数、和为定值的m个数、荷兰国旗、长度为2n的洗牌算法、任意长度数组的洗牌算法)

    Algorithm:C+语言实现之数组相关算法(和为定值的两个数.和为定值的m个数.荷兰国旗.长度为2n的洗牌算法.任意长度数组的洗牌算法) 目录 数组 1.寻找和为定值的两个数 2.和为定值的m个数 ...

  4. go实现数组切片洗牌函数Shuffle

    go实现数组切片洗牌函数Shuffle 在深度学习.机器学习中,我们经常会使用到一个叫 Shuffle 函数,我一般叫打乱函数,也有人叫 洗牌 函数,就是听着高级点, 它可以帮助我们打乱数据集,那么在 ...

  5. perl随机打乱数组

    perl有一个自带的随机打乱数组的库,使用方法如下: use List::Util; @array = List::Util::shuffle @array; 举一个例子: #! /usr/bin/p ...

  6. php将数组中元素打乱顺序,PHP公开课|学会随机打乱数组元素顺序的函数,你的PHP会学的更好...

    [摘要]PHP作为一种超文本预处理器,已经成为了我们常用的网站编程语言,并且结合了C语言,Java等我们常见的编程语言,所以,有很多web开发领域的新人都看中了他的使用广泛性,有很多人都想了解php的 ...

  7. 用sort()方法随机打乱数组

    2019独角兽企业重金招聘Python工程师标准>>> 我们先看一下随机数组排序的示例,再来研究其中的原理. 随机数组排序示例 var numbers = [5, 458 , 120 ...

  8. 会排序吗_洗牌算法详解:你会排序,但你会打乱吗?

    预计阅读时间: 8 分钟 我知道大家会各种花式排序,但是如果叫你打乱一个数组,你是否能做到胸有成竹?即便你拍脑袋想出一个算法,怎么证明你的算法就是正确的呢?乱序算法不像排序算法,结果唯一可以很容易检验 ...

  9. shuffle洗牌算法java_js打乱一个数组 的 洗牌(shuffle )算法

    写php的时候 有个shuffle  函数很爽很好用,但是js要实现同样算法的时候发现没有现成函数,在网上看了大量的例子,觉得不靠谱所以,自己写了一个,其实非常简单,以下是实现代码 var shuff ...

最新文章

  1. 成功解决IndexError: arrays used as indices must be of integer (or boolean) type
  2. 【论文解读】Graph Normalization (GN):为图神经网络学习一个有效的图归一化
  3. 提高电脑开机速度的一些基本操作
  4. js数组中的find、filter、sort
  5. [翻译] ZCSHoldProgress
  6. disruptor小结--消费者
  7. 2019JAVA中ajax不高亮_2019-01-18 ajax请求时,token死活放不大header中
  8. 计算机上的证书安装不了,数字证书认不到怎么办?
  9. 论文精读- The Evaluation of the Urban Road Network Based on the Complex Network
  10. js递归遍历json对象,js循环遍历json数组
  11. ◮OpenGL-抗锯齿
  12. iphone如何刷android系统升级,iphone4s如何刷成android系统?
  13. 物联网-通信模块使用笔记
  14. 怎么去掉input textarea 选中后的边线框,textarea 不可以拉
  15. Delphi 多线程编程(1)
  16. Linux系统替换文件内容
  17. 常用的系统操作响应时间
  18. paypal里的钱怎么取出来?
  19. 《三国志X PK版》2005年1月发售
  20. 用matlab代码实现QDA,matlab数据库

热门文章

  1. MT6580启动流程
  2. linux下多版本gcc编译器管理方法
  3. 8.7 使用索引-notes
  4. MySQL(10)数据库实现高可用架构之MHA
  5. 搭载鸿蒙处理器的手机,荣耀Magic3被曝光,或采用安卓与鸿蒙双系统,搭载麒麟9000处理器...
  6. P1314 聪明的质监员(前缀和+二分)
  7. flask返回json数据到前端_小白学Flask第六天| abort函数、自定义错误方法、视图函数的返回值...
  8. 9月26日数聚云端·智驭未来「阿里云数据库创新上云峰会」邀你进入数智未来
  9. 阿里云视图计算,边缘计算的主“战”场
  10. 小麦助教:通过阿里云原生中间件产品组合,加速微服务架构落地