天天看点

洗牌算法

洗牌算法一:

生成一个不重复的随机序列,将随机序列绑定到nums[],然后对随机序列做一次排序。

洗牌算法二:(经典洗牌算法)

for(int i=nums.length-1; i>=1; i--) 
  Swap(nums[i], nums[rand()%(i+1)]);       

经典算法的证明:

对于nums[i],洗牌后在第n-1个位置的概率是1/n(第一次交换的随机数为i)

在n-2个位置概率是[(n-1)/n] * [1/(n-1)] = 1/n,(第一次交换的随机数不为i,第二次为nums[i]所在的位置(注意,若i=n-1,第一交换nums[n-1]会被换到一个随机的位置))

在第n-k个位置的概率是[(n-1)/n] * [(n-2)/(n-1)] *...* [(n-k+1)/(n-k+2)] *[1/(n-k+1)] = 1/n

(第一个随机数不要为i,第二次不为nums[i]所在的位置(随着交换有可能会变)……第n-k次为nums[i]所在的位置)

洗牌算法三:(inside-out算法,可用于未知牌数)

类似于蓄水池抽样算法。

int i=0;
while(nums[i]存在)
{
	int k = rand()%(i + 1);
	res[i] = res[k];
	res[k] = nums[i++];

}
      

上面是伪代码,如果知道nums的lenght的话,可以改为for循环,由于是从前往后遍历,所以可以应对nums[]数目未知的情况,或者nums[]是一个动态增加的情况。

证明如下:

原数组的第 i 个元素在新数组的前 i 个位置的概率都是:(1/i) * [i/(i+1)] * [(i+1)/(i+2)] *...* [(n-1)/n] = 1/n,(即第i次刚好随机放到了该位置,在后面的n-i 次选择中该数字不被选中)

原数组的第 i 个元素在新数组的 i+1 (包括i + 1)以后的位置(假设是第k个位置)的概率是:(1/k) * [k/(k+1)] * [(k+1)/(k+2)] *...* [(n-1)/n] = 1/n(即第k次刚好随机放到了该位置,在后面的n-k次选择中该数字不被选中)

蓄水池抽样:

问题:如何随机从n个对象中选择一个对象,这n个对象是按序排列的,但是在此之前你是不知道n的值的。

解法:我们总是选择第一个对象,以1/2的概率选择第二个,以1/3的概率选择第三个,以此类推,以1/m的概率选择第m个对象。当该过程结束时,每一个对象具有相同的选中概率,即1/n.

继续阅读