介绍一个简单易懂然后又让你拍案叫绝的算法!

这个算法的代码量很少,却很惊艳。

它就是传说中的 洗牌算法 !

小技巧:看到一个好答案,想点赞又嫌麻烦,可以双击屏幕自动点,既能鼓舞作者,又能很方便自己下次再看。我用这个方法,已经快速标记 10 来个好文章了。


先来思考一个问题:有一个大小为 100 的数组,里面的元素是从 1 到 100 按顺序排列,怎样随机的从里面选择 1 个数?

最简单的方法是利用系统的方法 Math.random() * 100 ,这样就可以拿到一个 0 到 99 的随机数,然后去数组找对应的位置就即可。

接下来在思考一个问题: 有一个大小为100的数组,里面的元素是从 1 到 100 按顺序排列,怎样随机的从里面选择 50 个数?

注意数字不能重复!

注意数字不能重复!

注意数字不能重复!

如果根据上面的思路,你第一想法是:随机 50 次不就行了?

但是,这样做有个很明显的 bug :数字是会重复的。

修改一下?

弄一个数组,把每一次随机的数都放到数组里,下一次随机就看这个数组里面有没有这数,有的话就继续随机,直到这个数组里面有 50 个数字就停止。

这样是可以的!

但,还是有个小问题,考虑一下极端情况:有一个大小为100的数组,里面的元素是从 1 到 100 按顺序排列,怎样随机的从里面选择 99 个数。

如果按照上面的方法操作,越往后选择的数字跟前面已经挑选的数字重复的概率越高,这就会造成如果数组很大,选择的数字数目也很大的话,重复次数在量级上会很大。

这个时候就需要换一个思路,如果先将数组里面的元素打乱,那么按顺序选择前 50 个不就可以了?

是的!

但我们得注意什么叫乱?

一副扑克有 54 张牌,有 54! 种排列方式。所谓的打乱指的是,你所执行的操作,应该能够 等概率地生成 这 54! 种结果中的一种。

洗牌算法就能做到这一点。

洗牌算法

Fisher–Yates shuffle 算法由 Ronald Fisher 和 Frank Yates 于 1938 年提出,在 1964 年由 Richard Durstenfeld 改编为适用于电脑编程的版本。

这个算法很牛逼却很好理解,通俗的解释就是:将最后一个数和前面任意 n-1 个数中的一个数进行交换(也可不交换),然后倒数第二个数和前面任意 n-2 个数中的一个数进行交换。。。

小程序实现代码

for (var i = this.rowCount * this.colCount - 1; i >= 0 ; i--){ var iX = parseInt(i / this.colCount); var iY = i % this.colCount; var randNumber = this.rangeRandom(0, i + 1); var randX = parseInt(randNumber / this.colCount); var randY = randNumber % this.colCount; //交换两个位置 var temp = tmpMineMap[iX][iY]; tmpMineMap[iX][iY] = tmpMineMap[randX][randY]; tmpMineMap[randX][randY] = temp; }

在整个过程中,这个算法保证了每一个元素出现在每一个位置的概率是相等的。

这个算法真的很牛逼很经典,而且代码很少。


补充一篇介绍 Knuth 洗牌算法 神一样的随机算法。(作者, @刘宇波 )

这篇文章,我们从一道经典面试题开始来探讨这个问题。这个面试题有很多形式,但其实背后的算法是一致的。

这个问题是:设计一个公平的洗牌算法 1.

看问题,洗牌,显然是一个随机算法了。随机算法还不简单?随机呗。把所有牌放到一个数组中,每次取两张牌交换位置,随机 k 次即可。

如果你的答案是这样,通常面试官会进一步问一下,k 应该取多少?100?1000?10000?

很显然,取一个固定的值不合理。如果数组中有 1000000 个元素,随机 100 次太少;如果数组中只有 10 个元素,随机 10000 次又太多。一个合理的选择是,随机次数和数组中元素大小相关。比如数组有多少个元素,我们就随机多少次。

这个答案已经好很多了。但其实,连这个问题的本质都没有触及到。此时,面试官一定会狡黠地一笑:这个算法公平吗?

我们再看问题:设计一个 公平 的洗牌算法。 2.

问题来了,对于一个洗牌算法来说,什么叫“公平”?这其实是这个问题的实质,我们必须定义清楚:什么叫公平。

一旦你开始思考这个问题,才触及到了这个问题的核心。在我看来,不管你能不能最终给出正确的算法,如果你的思路是在思考对于洗牌算法来说,什么是“公平”,我都觉得很优秀。

因为背出一个算法是简单的,但是这种探求问题本源的思考角度,绝不是一日之功。别人告诉你再多次“要定义清楚问题的实质”都没用。这是一种不断面对问题,不断解决问题,逐渐磨炼出来的能力,短时间内无法培训。

这也是我经常说的,面试不是标准化考试,不一定要求你给出正确答案。面试的关键,是看每个人思考问题的能力。

说回我们的洗牌算法,什么叫公平呢?一旦你开始思考这个问题,其实答案不难想到。洗牌的结果是所有元素的一个排列。一副牌如果有 n 个元素,最终排列的可能性一共有 n! 个。公平的洗牌算法,应该能等概率地给出这 n! 个结果中的任意一个。

如思考虑到这一点,我们就能设计出一个简单的暴力算法了:对于 n 个元素,生成所有的 n! 个排列,然后,随机抽一个。

这个算法绝对是公平的。但问题是,复杂度太高。复杂度是多少呢?O(n!)。因为,n 个元素一共有 n! 种排列,我们求出所有 n! 种排列,至少需要 n! 的时间。

有一些同学可能对 O(n!) 没有概念。我本科时就闹过笑话,正儿八经地表示 O(n!) 并不是什么大不了不起的复杂度。实际上,这是一个比指数级 O(2^n) 更高的复杂度。因为 2^n 是 n 个 2 相乘;而 n! 也是 n 个数字相乘,但除了 1,其他所有数字都是大于等于 2 的。当 n>=4 开始,n! 以极快的的速度超越 2^n。

O(2^n) 已经被称为指数爆炸了。O(n!) 不可想象。

所以,这个算法确实是公平的,但是,时间不可容忍。 3.

我们再换一个角度思考“公平”这个话题。

其实,我们也可以认为,公平是指,对于生成的排列,每一个元素都能独立等概率地出现在每一个位置。或者反过来,每一个位置都能独立等概率地放置每个元素。

基于这个定义,我们就可以给出一个简单的算法了。说这个算法简单,是因为他的逻辑太容易了,就一个循环:

for(int i = n - 1; i >= 0 ; i -- ) swap(arr[i], arr[rand(0, i)]) // rand(0, i) 生成 [0, i] 之间的随机整数

这么简单的一个算法,可以保证上面我所说的,对于生成的排列,每一个元素都能独立等概率的出现在每一个位置。或者反过来,每一个位置都能独立等概率的放置每个元素。

大家可以先简单的理解一下这个循环在做什么。其实非常简单,i 从后向前,每次随机一个 [0...i] 之间的下标,然后将 arr[i] 和这个随机的下标元素,也就是 arr[rand(0, i)] 交换位置。 大家注意,由于每次是随机一个 [0...i] 之间的下标,所以,在每一轮,是可以自己和自己交换的。 这个算法就是大名鼎鼎的 Knuth-Shuffle,即 Knuth 洗牌算法。

这个算法的原理,我们稍后再讲。先来看看 Knuth 何许人也?

中文名:高纳德。算法理论的创始人。我们现在所使用的各种算法复杂度分析的符号,就是他发明的。上世纪 60-70 年代计算机算法的黄金时期,近乎就是他一手主导的。他的成就实在太多,有时间单独发文介绍,但是,我觉得一篇文章是不够的,一本书还差不多。

大家最津津乐道的,就是他所写的《The Art of Computer Programming》,简称 TAOCP。这套书准备写七卷本,然后,到今天还没有写完,但已经被《科学美国人》评为可以媲美相对论的巨著。

微软是 IT 界老大的年代,比尔盖茨直接说,如果你看完了这套书的第一卷本,请直接给我发简历。

至于这套书为什么写的这么慢?因为老爷子写到一半,觉得当下的文字排版工具都太烂,于是转而发明出了现在Tex文字排版系统,这就是现在大名鼎鼎的 LaTeX 的雏形...

另外,老爷子可能觉得当下的编程语言都不能完美地表现自己的逻辑思想,还发明了一套抽象的逻辑语言,用于这套书中的逻辑表示...

下面这张照片是他年轻的时候。这张照片是我在斯坦福大学计算机学院的橱窗拍的。

下面的话和大家共勉: A programmer who subconsciously views himself as an artist will enjoy what he does and will do it better. Donald E. Knuth 1978 所以,我从来都不认为自己只是一名工程师而已。我是艺术家:)

4.

是时候仔细的看一下,这个简单的算法,为什么能做到保证:对于生成的排列,每一个元素都能等概率的出现在每一个位置了。 其实,简单的吓人:) 在这里,我们模拟一下算法的执行过程,同时,对于每一步,计算一下概率值。 我们简单的只是用 5 个数字进行模拟。假设初始的时候,是按照 1,2,3,4,5 进行排列的。

那么,根据这个算法,首先会在这五个元素中选一个元素,和最后一个元素 5 交换位置。假设随机出了 2。

下面,我们计算 2 出现在最后一个位置的概率是多少?非常简单,因为是从 5 个元素中选的嘛,就是 1/5。实际上,根据这一步,任意一个元素出现在最后一个位置的概率,都是 1/5。

下面,根据这个算法,我们就已经不用管 2 了,而是在前面 4 个元素中,随机一个元素,放在倒数第二的位置。假设我们随机的是 3。3 和现在倒数第二个位置的元素 4 交换位置。

下面的计算非常重要。3 出现在这个位置的概率是多少?计算方式是这样的:

其实很简单,因为 3 逃出了第一轮的筛选,概率是 4/5,但是 3 没有逃过这一轮的选择。在这一轮,一共有4个元素,所以 3 被选中的概率是 1/4。因此,最终,3 出现在这个倒数第二的位置,概率是 4/5 * 1/4 = 1/5。 还是 1/5 ! 实际上,用这个方法计算,任意一个元素出现在这个倒数第二位置的概率,都是 1/5。 相信聪明的同学已经了解了。我们再进行下一步,在剩下的三个元素中随机一个元素,放在中间的位置。假设我们随机的是 1。

关键是:1 出现在这个位置的概率是多少?计算方式是这样的:

即 1 首先在第一轮没被选中,概率是 4/5,在第二轮又没被选中,概率是 3/4 ,但是在第三轮被选中了,概率是 1/3。乘在一起,4/5 * 3/4 * 1/3 = 1/5。 用这个方法计算,任意一个元素出现在中间位置的概率,都是 1/5。 这个过程继续,现在,我们只剩下两个元素了,在剩下的两个元素中,随机选一个,比如是4。将4放到第二个位置。

然后,4 出现在这个位置的概率是多少?4 首先在第一轮没被选中,概率是 4/5;在第二轮又没被选中,概率是 3/4;第三轮还没选中,概率是 2/3,但是在第四轮被选中了,概率是 1/2。乘在一起,4/5 * 3/4 * 2/3 * 1/2 = 1/5。

用这个方法计算,任意一个元素出现在第二个位置的概率,都是 1/5。

最后,就剩下元素5了。它只能在第一个位置呆着了。

那么 5 留在第一个位置的概率是多少?即在前 4 轮,5 都没有选中的概率是多少?

在第一轮没被选中,概率是 4/5;在第二轮又没被选中,概率是 3/4;第三轮还没选中,概率是 2/3,在第四轮依然没有被选中,概率是 1/2。乘在一起,4/5 * 3/4 * 2/3 * 1/2 = 1/5。

算法结束。 你看,在整个过程中,每一个元素出现在每一个位置的概率,都是 1/5 !

所以,这个算法是公平的。

当然了,上面只是举例子。这个证明可以很容易地拓展到数组元素个数为 n 的任意数组。整个算法的复杂度是 O(n) 的。

通过这个过程,大家也可以看到,同样的思路,我们也完全可以从前向后依次决定每个位置的数字是谁。不过从前向后,代码会复杂一些,感兴趣的同学可以想一想为什么?自己实现一下试试看?

(因为生成 [0, i] 范围的随机数比生成 [i, n) 范围的随机数简单,直接对 i+1 求余就好了。)

有没有被惊艳到?值不值得拍案叫绝一下?!

有哪些令人拍案叫绝的算法?相关推荐

  1. 令人拍案叫绝的算法学习网站新手算法入门到精通,算法面试冲刺资料这里都有

    (9月已更)学算法认准这6个网站就够了! 写在前面:作为ACM铜牌选手,从FB到腾讯,从事算法&java岗位工作也是5年有余.在工作中接触到了很多同学,在算法学习和算法面试这件事上我还是很有发 ...

  2. 令人拍案叫绝的算法学习网站,算法入门到精通,算法面试冲刺资料这里都有

    前言 作为ACM铜牌选手,从FB到腾讯,从事算法&java岗位工作也是5年有余.在工作中接触到了很多同学,在算法学习和算法面试这件事上我还是很有发言权的. 今天就跟想学算法的同学分享一下我私藏 ...

  3. 令人拍案叫绝的Wasserstein GAN

    雷锋网按:本文作者郑华滨,原载于知乎.雷锋网已获转载授权. 在GAN的相关研究如火如荼甚至可以说是泛滥的今天,一篇新鲜出炉的arXiv论文<Wassertein GAN>却在Reddit的 ...

  4. 令人拍案叫绝的 Wasserstein GAN,彻底解决GAN训练不稳定问题

    [新智元导读] 本文详细解析了最近在 reddit 的 Machine Learning 版引起热烈讨论的一篇论文Wassertein GAN,该论文提出的 WGAN 相比原始 GAN 的算法实现流程 ...

  5. [转载]--令人拍案叫绝的Wasserstein GAN

    文章转载自: https://zhuanlan.zhihu.com/p/25071913 令人拍案叫绝的Wasserstein GAN 郑华滨 · 2 天前 在GAN的相关研究如火如荼甚至可以说是泛滥 ...

  6. 深入理解 python 虚拟机:令人拍案叫绝的字节码设计

    深入理解 python 虚拟机:令人拍案叫绝的字节码设计 在本篇文章当中主要给大家介绍 cpython 虚拟机对于字节码的设计以及在调试过程当中一个比较重要的字段 co_lnotab 的设计原理! p ...

  7. 深度有趣 | 16 令人拍案叫绝的WGAN

    简介 在DCGAN的基础上,介绍WGAN的原理和实现,并在LFW和CelebA两个数据集上进一步实践 问题 GAN一直面临以下问题和挑战 训练困难,需要精心设计模型结构,并小心协调G和D的训练程度 G ...

  8. 一种令人拍案叫绝的 ChatGPT 攻击手段!

    公众号关注 "GitHubDaily" 设为 "星标",每天带你逛 GitHub! 最近看到一个非常巧妙的 ChatGPT 攻击手段,跟大家分享一下,也算是做个 ...

  9. 一道让你拍案叫绝的算法题

    这是一道看完答案会觉得很简单,但做之前很难想到答案的题目!!! 不信? Let us go ! 题目描述 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次.找出那个只出现了一次 ...

最新文章

  1. 二分图带权最大匹配费用流_一文掌握阻抗匹配
  2. PyTorch环境下对BERT进行Fine-tuning
  3. Webservice 或者HttpRequest请求的时候提示 “指定的注册表项不存在”错误 解决方案...
  4. 将txt文件内容通过cgi和apache显示在网页上
  5. rto净化效率计算公式_全面剖析 石油化工行业RTO蓄热式焚烧炉的优势要素
  6. micopython 18b20_[MicroPython]stm32f407控制DS18B20检测温度
  7. POJ 2993 Emag eht htiw Em Pleh(模拟)
  8. joomla第一单元第四节K2类别设置和第五节项目视图选项
  9. iPhone13系列预计5499起;蔚来回应31岁企业家“自动驾驶”车祸去世;小米取消MIX4防丢失模式无卡联网服务|极客头条...
  10. Android RecyclerView网格布局动画
  11. oracle取本月最后一天是星期几_在oracle里,如何取得本周、本月、本季度、本年度的第一天和最后一天的时间...
  12. 取消计算机触摸板,笔记本电脑触摸板如何打开和关闭
  13. python编码无法使用turtle库_使用Turtle库教Python
  14. 分享图片至Facebook与Twitter
  15. Android仿微信朋友圈7实现点赞功能
  16. 蓝桥杯-第六届省赛第一题
  17. 由一道考研基础题引发的关于对(函数导数符号在内外的区别)f‘(x)和[f(x)]‘的区别思考
  18. 线性方程组AX=b,AX=0以及非线性方程组的最小二乘解(解方程组->优化问题)
  19. ZZULIOJ1000-1010
  20. VSCode中修改各级MD标题配色

热门文章

  1. 寒冬降临程序猿们如何过冬
  2. 混迹在腾讯微博的知名站长名单(截至4月28日)
  3. 开启电脑替我记忆之路
  4. React 之导入 Excel
  5. 小汪汪服务器不稳定,小汪汪闪退了怎么办 小汪汪闪退问题解决办法
  6. mapbox pbf vt2geojson 解析pbf图层为geojson格式
  7. 锐龙7000PBO温度墙设置
  8. man fputc fputs putc putchar puts
  9. mac系统python读取文件路径_python读取文件常见问题(Mac版)
  10. 湖南农业大学计算机考试试题,湖南农业大学机械CADCAM考试复习题