洗牌算法与水塘抽样
写在最前:要注意洗牌算法与水塘采样算法之间的区别
水塘抽样是一系列的随机算法,其目的在于从包含n个项目的集合S中选取k个样本,其中n为一很大或未知的数量,尤其适用于不能把所有n个项目都存放到主内存的情况。
洗牌算法就是将数据完全打乱的一种算法思想,类似于我们打扑克时候的洗牌。
洗牌算法
public void shuffle(int[] arr){
int n = arr.length;
Random rdm = new Random();
for(int i = 0;i<n;i++){
// [0,n)
int rand = rdm.nextInt(i,n);
/**
错误写法:int rand = rdm.nextInt(0,n);
这样子的话,总共有n**n种可能,而不是n!
*/
swap(arr,i,rand);
}
}
public void swap(int[] arr,int i,int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
怎么能够说明确实是随机的呢?
首先,完全随机的话,\(n\)个数字的排列应该有\(n!\)个全排列,如果我们能够说明数组中的元素是我们是从这\(n!\)中等概率选择的就可以了。
然后,针对于代码的第6行,产生\([i,n)\)的随机数,然后将产生的随机数加入到已选的数组中,第一次是从\([0,n)\)中选,有\(n\)中可能,第二次是从\([1,n)\)中选,有\(n-1\)种可能,以此类推得证。
水塘抽样
//返回链表中随机的k个节点的值
public int[] getRandom(ListNode head,int k){
Random rdm = new Random();
int[] res = new int[k];
ListNode p = head;
// 前k个元素先默认选上
for(int i = 0;i<k && p!=null;i++){
res[i] = p.val;
p=p.next;
}
int i = k;
// 循环遍历链表
while(p!=null){
// 生成一个[0,i)之间的随机数
int j = rdm.nextInt(++i);
// 随机生成的这个数小于k的概率就是k/i
if(j<k) res[j] = p.val;
p=p.next;
}
return res;
}
当你遇到第\(i\) 个元素时,应该有 \(\frac{1}{i}\) 的概率选择该元素,\(1 -\frac{1}{i}\) 的概率保持原有的选择.
同理,如果要随机选择 \(k\) 个数,只要在第\(i\) 个元素处以 \(\frac{k}{i}\) 的概率选择该元素,以 \(1 - \frac{k}{i}\) 的概率保持原有选择即可
Saying Less Doing More

浙公网安备 33010602011771号