Knuth-Shuffle:一个公平的洗牌算法
问题介绍
洗牌,简单来说就是随机交换牌的位置,但是如何才是公平的呢,洗牌的结果是所有元素的一个排列。一副牌如果有 n 个元素,最终排列的可能性一共有 n! 个。公平的洗牌算法,应该能等概率地给出这 n! 个结果中的任意一个。这样的暴力解法时间复杂度为O(n!),不可取。公平是说,对于每一个排列,每一个元素都能等概率的出现在每一个位置,也可以反过来,每一个位置都能等概率的放置每一个元素。基于此,我们可以设计出下面的算法。
算法实现
import java.util.Arrays;
public class KnuthShuffle {
/**
* 洗牌
*/
public void shuffle(int[] nums) {
for (int i = nums.length - 1; i >= 0; i--) {
swap(nums, i, (int) (Math.random() * (i + 1)));
}
}
private void swap(int[] nums, int left, int right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
public static void main(String[] args) {
int[] nums = {1, 2, 3, 4, 5, 6, 7, 8, 9};
new KnuthShuffle().shuffle(nums);
System.out.println(Arrays.toString(nums));
}
}
上面的算法就是大名鼎鼎的Knuth 洗牌算法。
第一次循环从1,2,3,4,5之间随机选出一个数,假设选出为2,和最后的5交换,任意一个元素出现在最后一个位置的概率都是1/5,
第二次循环从1,5,3,4之间随机选出一个数,假设为3,和最后的4交换,3没有满足第一次循环的概率是4/5,满足这一次循环的概率是1/4,最终为4/5*1/4=1/5。
以此类推,每一个位置放置每一个元素的概率都是1/5。
java中Collections.shuffle()就是使用的这种算法。