Knuth Shuffle

  • 问题描述
  • 算法描述
  • 算法实现
  • 算法证明
  • 算法分析
  • 延伸问题
    • 不改变原有数组
      • 算法描述
      • 算法实现
      • 算法证明
    • 未知长度的原始数组
      • 算法实现
    • 产生一个随机的环排列
      • 算法描述
      • 算法实现
      • 算法证明
    • 实践中可能发生的问题
      • 取模的概率不均等
      • 伪随机

问题描述

Knuth Shuffle,也称作Fisher-Yates Shuffle,是一种对一个有限序列生成随机排列的算法。

算法描述

假设原数组为a[]a[]a[],长度为nnn,从最后一个元素开始,产生一个000到n−1n-1n−1的随机整数jjj,并将交换a[j]a[j]a[j]与a[n−1]a[n-1]a[n−1]的值;这样便确定了最后结果中a[n−1]a[n-1]a[n−1]的值;之后,产生下一个000到n−2n-2n−2的随机整数jjj,交换a[j]a[j]a[j]与a[n−2]a[n-2]a[n−2]的值,这样便确定了最后结果中a[n−2]a[n-2]a[n−2]的值;重复这个步骤直到确定a[1]a[1]a[1]。

算法实现

public void knuthShuffle(int[] a) {for (int i = a.length - 1; i > 0; i -= 1) {int j = StdRandom.Uniform(i + 1);//Swap(a[j], a[i])int temp = a[i];a[i] = a[j];a[j] = temp;}
}

算法证明

从第一次循环可以看出,任何一个元素出现在a[n−1]a[n-1]a[n−1]的概率是均等的,都是1n\frac{1}{n}n1​。在第二次循环中,任何一个元素出现在a[n−2]a[n-2]a[n−2]的概率为(1−1n)×1n−1=1n(1-\frac{1}{n})\times\frac{1}{n-1}=\frac{1}{n}(1−n1​)×n−11​=n1​。以此类推,可以得到任意一个元素出现在a[k]a[k]a[k]的概率均为1n\frac{1}{n}n1​。证毕。

算法分析

显然,Knuth Shuffle的时间复杂度为O(n)O(n)O(n),空间复杂度为O(1)O(1)O(1)。同时要注意的是,Knuth Shuffle是一个in-place的shuffle。

延伸问题

不改变原有数组

这要求我们一边初始化数组,一边产生随机排列。

算法描述

从0开始一直到n−1n-1n−1,每次循环中初始化并确定a[i]a[i]a[i]的值。在循环中,产生一个000到iii的随机整数jjj,如果j!=ij!=ij!=i,那么将a[j]a[j]a[j]赋给a[i]a[i]a[i](注意到此时必有j<ij<ij<i,所以在之前的循环中a[j]a[j]a[j]已经被初始化过了)。之后,将source[j]source[j]source[j]赋给a[j]a[j]a[j]。注意到最后一步中,如果之前j=ij=ij=i,那么a[i]a[i]a[i]正好用source[j]source[j]source[j],也就是source[i]source[i]source[i]初始化。

算法实现

public int[] knuthShuffleInsideOut(int[] Source) {int[] a = new int[Source.length()];for (int i = 0; i < Source.length; i++) {int j = StdRandom.Uniform(i + 1);if ( j != i ) a[i] = a[j];a[j] = Source[i];}return a;
}

算法证明

证明的一种方法是数学归纳法。如果source[]source[]source[]的长度为1,显然这个算法是正确的。假定这个算法对长度为kkk的source[]source[]source[]成立,则前kkk次循环中已经洗好了source[]source[]source[]的前kkk位,在最后一次循环中,显然任意一个元素出现在任意位置的概率是均等的。因此这个算法对长度为k+1k+1k+1的source[]source[]source[]也正确,证毕。
另一种证明方法是考虑算法中的随机数jjj,考虑循环中产生出的nnn个jjj,显见j[0]=0j[0]=0j[0]=0,j[1]={0,1}j[1]=\{0,1\}j[1]={0,1},…,j[n]={0,1,2,...,n}j[n] = \{0, 1, 2, ... , n\}j[n]={0,1,2,...,n}。所以j[]j[]j[]一共有n!n!n!种可能,且每种可能是等概率的。而任意两个不同的j[]j[]j[]一定会产生不同的a[]a[]a[]。这是因为考虑这两个j[]j[]j[]最后一个不同的元素j[i]j[i]j[i],显然这表明source[i]source[i]source[i]在这两个j[]j[]j[]生成的a[]a[]a[]中被分配到了不同的位置上。所以这两个数组也一定不同。而我们知道长度为nnn的source[]source[]source[]的全排列一共也有n!n!n!种。这表明每一个j[]j[]j[]对应着一种排类,而且每一种排列出现的概率相等。证毕。

未知长度的原始数组

考察上面的算法,发现我们只在两个地方使用到了原始数组的长度,其一是初始化数组a[]a[]a[]时,其二是用在了for循环的结束条件中。那么如果原始数组是一个数据流,且我们不知道其长度,如何改变算法呢?
对于第一处使用,我们可以使用可变长度的数组arrayList,对于第二处使用,显然for循环的结束条件可以改为数据流结束。因此,我们可以得到如下的算法。

算法实现

public ArrayList<Integer> knuthShuffleUnknownLength(String[] args) {ArrayList<Integer> a = new ArrayList<>();while (!StdIn.isEmpty()) {int intFromIn = StdIn.readInt();int j = StdRandom.Uniform(a.size() + 1);if ( j == a.size() ) a.add(intFromIn);else {a.add(a.get[j]);a.set(j, intFromIn);}}
}

产生一个随机的环排列

一个数组的一种排列可以看成是对index的一种映射,例如[1,3,4,2],可以看成f(1)=1,f(2)=3,f(3)=4,f(4)=2f(1) = 1, f(2) = 3, f(3) = 4, f(4) = 2f(1)=1,f(2)=3,f(3)=4,f(4)=2。于是这个数组中存在两个环1−>1−>11->1->11−>1−>1以及2−>3−>4−>22->3->4->22−>3−>4−>2。如果这个数组中只有一个环,则我们称之为一个环排列。例如[2,3,4,1]中只存在2−>3−>4−>12->3->4->12−>3−>4−>1一个环,则它是一个环排列。可以证明,一个数组的环排列的个数为(n−1)!(n-1)!(n−1)!。(元素1寻找自己的下一个映射有n−1n-1n−1种选择,选好后,这个元素寻找自己的下一个映射有n−2n-2n−2种选择…)

算法描述

这个算法称之为Sattolo算法,与Knuth Shuffle的唯一不同在于,在循环中我们产生000到i−1i-1i−1的随机整数,而非000到iii。亦即不会选中自己。

算法实现

public void knuthShuffle(int[] a) {for (int i = a.length - 1; i > 0; i -= 1) {int j = StdRandom.Uniform(i);//Swap(a[j], a[i])int temp = a[i];a[i] = a[j];a[j] = temp;}
}

算法证明

首先我们证明Sattolo算法一定会产生一个环排列。我们知道,初始数列中有nnn个长度为111的环,即每个元素都映射到自己。第一次循环中,最后一个数与之前的一个数发生交换,使得两个环merge成了一个环(a[j]−>a[n−1]−>a[j]a[j]->a[n-1]->a[j]a[j]−>a[n−1]−>a[j]),环的总数下降为了n−1n-1n−1。在下一个循环中,前n−1n-1n−1个数中同样是n−1n-1n−1个长度为111的环,在交换后,环的数量再次下降了一个。因此,经过n−1n-1n−1次循环后,环的数量下降为111,所以我们证明了这是一个环排列。
再来证明这个算法均等地产生了所有环排列。因为环排列的种数为(n−1)!(n-1)!(n−1)!,而在n−1n-1n−1次循环中我们产生了(n−1)(n-1)(n−1)个不同的j[]j[]j[],且每种j[]j[]j[]都对应不同的排列,每种j[]j[]j[]产生的概率都相等。因此我们证明了Sattolo算法均等地给出了所有环排列。证毕。

实践中可能发生的问题

这个部分主要参考了维基百科。除了在实现过程中错误地指定了随机数的范围外,这个算法可能会因为系统实现随机数的一些问题而导致出现偏差。

取模的概率不均等

考虑到一些随机数的实现方式总是产生[0,RandMax]中的随机整数,而后通过取模的方式让这个随机整数落在[0, n]上,如果nnn不能整除RandMax,那么这个随机整数的产生概率是有偏差的,并且会趋向于较小的余数。

伪随机

一个数组的全排列个数有n!n!n!种,如果该伪随机数表的状态有qqq种,则它只能产生2q2^q2q种不同序列的随机数数组。例如一个只有32种状态的伪随机数产生器是不能产生长度为52的数组(一副扑克牌)的全排列的(因为232<<52!2^{32}<<52!232<<52!)。

Knuth Shuffle相关推荐

  1. 关于Knuth Shuffle算法

    这个算法是我前几天才听说的,觉得挺有意思,来写一写.好像出处是TAOCP,但我没看过.#(快哭了) 有的时候我们需要打乱一个排列的顺序,比方说在机器学习里面我们通常都会对一个数据集进行shuffle. ...

  2. 从洗牌算法谈起--Python的random.shuffle函数实现原理

    此文首发于我的个人博客:从洗牌算法谈起–random.shuffle实现原理 - zhang0peter的个人博客 昨天看知乎的时候看到了洗牌算法(Knuth shuffle, 最初版本叫Fisher ...

  3. Knuth 洗牌算法

    核心思想 洗牌算法(Knuth shuffle算法):对于有n个元素的数组来说,为了保证洗牌的公平性,应该要能够等概率的洗出n!种结果. 举例解释如下: 开始数组中有五个元素: 在前五个数中随机选一个 ...

  4. 注册中心 Eureka 源码解析 —— 应用实例注册发现(五)之过期

    2019独角兽企业重金招聘Python工程师标准>>> 摘要: 原创出处 http://www.iocoder.cn/Eureka/instance-registry-evict/ ...

  5. 微服务治理之Eureka--源码浅析

    Eureka集群项目介绍 图中表示Eureka集群的整个动作流程.集群内有3个节点.3个应用服务,服务列表使用的是双层Map,第一层key->服务名,第二层key->实例名,value是实 ...

  6. Eureka实例自动过期

    本文来说下Eureka实例自动过期 文章目录 初始化配置 EvictionTask Lease.isExpire() 分批过期机制 本文小结 初始化配置 在Eureka-Server启动的时候,会启动 ...

  7. Eureka Server启动源码分析

    本文来分析下Eureka Server启动源码 文章目录 概述 源码解析 服务同步 服务剔除 start包配置 启动源码分析 EurekaServerAutoConfiguration EurekaS ...

  8. Spring Cloud Eureka 源码分析(一) 服务端启动过程

    2019独角兽企业重金招聘Python工程师标准>>> 一. 前言 我们在使用Spring Cloud Eureka服务发现功能的时候,简单的引入maven依赖,且在项目入口类根据服 ...

  9. Go语言(golang)开源项目大全

    http://www.open-open.com/lib/view/open1396063913278.html#Compression 内容目录 Astronomy 构建工具 缓存 云计算 命令行选 ...

最新文章

  1. CSS布局之float浮动
  2. Flink 异步IO访问外部数据(mysql篇)
  3. 就业阶段-java语言进价_day03
  4. 重温《数据库系统概论》【第一篇 基础篇】【第5章 数据库完整性】
  5. 你用苹果手机多长时间清理一次内存,怎么清理?
  6. Python入门--列表生成式
  7. Ubuntu远程办公 -- 设置SSH服务
  8. SilverLight学习之基本图形
  9. 垂直居中小记 line-height table vertical-align:middle
  10. 常用的VBA代码参考
  11. android 5.0rom官方,Android 5.0刷机包开放下载 升级需谨慎
  12. 用彩信模块发图片问题总结(STM32)
  13. dq坐标系下无功功率表达式_基于瞬时电流分解的谐波电流检测方法研究
  14. 从软件工程师到IT猎头
  15. 想当站长请立即使用Orchard
  16. 计算机仿真cad答案,计算机仿真技术及CAD
  17. WebInspect
  18. 555定时器(1)单稳态触发器电路及Multisim实例仿真
  19. analog_gain(模拟增益) 与digital_gain(数字增益)的区别与联系
  20. 美国散户从90降到6他们如被灭

热门文章

  1. 基于python实现的迷宫游戏
  2. 【RSVP-BCI基本知识点】
  3. 机器人主轴-雕刻、铣削、切割加工电主轴德国Jager
  4. Ubantu安装KDE桌面环境_下载KDE主题
  5. Prometheus监控:rate与irate的区别
  6. 宝塔linux面板php配置教程,宝塔Linux面板——新手安装教程
  7. 图像分类模型训练image retrain
  8. Centos7做软RAID5详细步骤
  9. 像素、设备像素比、PPI、Viewport
  10. Navicat 12 for MongoDB破解步骤