Fisher-Yates洗牌算法是由 Ronald A.Fisher和Frank Yates于1938年发明的,后来被Knuth在书中介绍,很多人直接称Knuth洗牌算法, Knuth大家应该比较熟悉,《The Art of Computer Programming》作者,算法理论的创始人。我们现在所使用的各种算法复杂度分析的符号,就是他发明的。
等概率: 洗牌算法有些人也称等概率洗牌算法,其实发牌的过程和我们抽签一样的,大学概率论讲过抽签是等概率的,同样洗牌算法选中每个元素是等概率的。
用洗牌算法思路从1、2、3、4、5这5个数中,随机取一个数
4被抽中的概率是1/5
5被抽中的概率是1/4 * 4/5 = 1/5
2被抽中的概率是1/3 * 3/4 * 4/5 = 1/5
1被抽中的概率是1/2 * 1/3 * 3/4 * 4/5= 1/5
3被抽中的概率是1 * 1/2 * 1/3 * 3/4 * 4/5 = 1/5
时间复杂度为O(n^2), 空间复杂度为O(n)
代码如下:
//O(N^2)time
//O(N)space
void test(int n, int m) {
List<Integer> list = new ArrayList<>();
for (int i = 1; i <= n; i++) {
list.add(i);
}
for (int i = 0; i < m; i++) {
int t = (int) (list.size() * Math.random());
System.out.println(list.remove(t));
}
}
在上面的介绍的发牌过程中, Knuth 和 Durstenfeld 在Fisher 等人的基础上对算法进行了改进,在原始数组上对数字进行交互,省去了额外O(n)的空间。该算法的基本思想和 Fisher 类似,每次从未处理的数据中随机取出一个数字,然后把该数字放在数组的尾部,即数组尾部存放的是已经处理过的数字。
时间复杂度为O(n), 空间复杂度为O(n)
//O(N)time
//O(N)space
void knuth(int n, int m) {
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = i + 1;
}
for (int i = 0; i < m; i++) {
int index = (int) ((n - i) * Math.random());
System.out.println(arr[i + index]);
swap(arr, i, i + index);
}
}
void swap(int[] arr, int i, int index) {
if (i == index) return;
int temp = arr[i];
arr[i] = arr[index];
arr[index] = temp;
}