Knuth-Shuffle算法
问题描述:现在有\(n\)个元素存放在一个数组里,给出一种算法能够生成它们的一个排列,使得生成每种排列的概率相等。
主要参考:《数据结构与算法分析——C语言描述》 Mark Allen Weiss
Knuth-Shuffle算法
不失一般性,可以记这n个元素就是整数1到n,存放在数组\(A[0...n-1]\)里。并且假设计算模型里有取随机数的基本操作:
1 | // 能够以相等概率随机返回一个范围在[i,j]的整数 |
这里直接给出算法的伪代码:
1 | void Knuth_Shuffle(int A[], int n) |
不难分析得到这个算法的时间复杂度是\(O(n)\),空间复杂度如果不计原本的数组则是\(O(1)\)的。
算法正确性的证明
首先这个算法涉及的操作就是对\(A[0...n-1]\)里的元素进行swap()
,最后得到的当然是这n个元素的一个排列。
记任意一个排列为\(a_0,a_1,...a_{n-1}\),为了得到这个排列,算法必须第一步用元素\(A[n-1]\)和元素\(a_{n-1}\)交换,实现这一步的概率显然是\(\frac{1}{n}\);第二步必须用元素\(a_{n-2}\)和元素\(A[n-2]\)交换,而这一步实际上是一个条件概率\(\frac{1}{n-1}\);以此类推,第k步必须用元素\(a_{n-k}\)和元素\(A[n-k]\)交换。
设事件\(E_k\)为算法的第k次迭代时用元素\(a_{n-k}\)和元素\(A[n-k]\)交换,则生成排列\(a_0,a_1,...a_{n-1}\)就是事件\(\prod_{k=0}^{n-1} E_k\)。展开一下就有
\[ P\{\prod_{k=0}^{n-1} E_k\} = P\{E_k\}P\{E_{k-1}|E_{k}\}...P\{E_{0}|E_{k}E_{k-1}...E_{1}\} \] 根据上面的分析不难发现这个结果就是\(\frac{1}{n!}\),所以每种排列生成的概率是一样的。
其他
在《数据结构与算法分析——C语言描述》中译本P27给出的算法其实是
1 | void Knuth_Shuffle(int A[], int n) |
和最开头给出的有一点点区别,但不难证明结论是一样的。
(Hint:从最后一次迭代往前分析即可)