首页 > 代码库 > 洗牌算法与蓄水池抽样

洗牌算法与蓄水池抽样

今儿看到了,就在此记录一下吧。

洗牌算法

递归做法:先将1~n-1洗牌,然后取随机数k(0<k<n),并交换n与k,代码很简单:

1 int[] shuffle(int[] cards, int n){2     if(n == 1){3         return cards;4     }5     shuffle(cards, n-1);6     int k = random(1, n-1);7     swap(card[k], card[n]);8     return card;      9 }

也可以转成非递归的:

1 void shuffle(int[] cards){2     for(int i = 0; i < cards.length; i++){3         int k = rand(0, i);4         int temp = cards[k];5         cards[k] = cards[i];6         cards[i] = temp;7     }8 }

 

下面转自http://hankjin.blog.163.com/blog/static/3373193720109141128016/

 

方法一:
1。随机产生一个1~n的数x,做为第一张牌。
2。随机产生一个1~(n-1)的数y,如果y<x,则将y作为第二张牌,否则将y+1作为第二张牌。
3。随机产生一个1~(n-i)的数z,取第z个没有被抽出来的作为第i张牌。(i=3,4,5...54)
这种算法的复杂度为O(N^2),因为计算每个随机数的牌号平均要执行(N/2)次比较。
对应于现实中的扑克牌,这种算法等于每次从牌堆中随机抽一张,放到另一堆上,直到抽完为止,这里新的一堆就是洗完的牌序。

方法二:
1。随机产生一个1-n的数x,然后让第x张牌和第1张牌互相调换。
2。随机产生一个1-n的数y,然后让第y张牌和第2张牌互相调换。
3。随机产生一个1-n的数z,然后让第z张牌和第i张牌互相调换。(i=3,4,5...54)
这种算法的复杂度为O(N)。

方法三:
因为一共有N!种洗牌结果,所以可以等概率地产生一个1-N!之间的随机数x,然后用康托展开的方法,根据x生成对应的排列,即为洗牌结果。
关于康托展开,参考http://baike.baidu.com/view/437641.htm,这个做法的复杂度是O(lgn)么?

方法二的修正:
方法二乍看好像不错,网上也有很多文章使用这种算法,但是其实这是一种错误的方法,因为方法二的所有可能性为N^N,而洗好的牌一种有N!种可能,又因为N^N % N! !=0,所以每种结果的概率是不相同的。
那么如何修正这个问题呢?仿照第一种方法,第i次洗牌不是产生一个1-n的随机数,而是产生一个i-n的随机数,这样可能性结果的可能性就是N!了。就有可能概率相等了。

也就是说,在方法二的第三行描述中第z张牌与第i张牌互换,因为z是1~n的,可能等于i,所以可能性就变成了N^N,所以是不正确的,改进之后的算法和前面我写的是差不多的。


 

蓄水池抽样

从n元素里随机取k个,做法如下:前k个全部取出放在结果集中,然后继续取,第i个以k/i的概率与结果集中随机的一个交换,具体做法如下:

case 1:当 0<i<=k 时,R[i] = S[i];

case 2:当 k<i<n 时,j = random(1,i); if(j<=k){ R[j]= S[i];}

证明转自:http://blog.csdn.net/huagong_adu/article/details/7619665

 

证明:第m个对象被选中的概率=选择m的概率*(其后元素不被选择的概率+其后元素被选择的概率*不替换第m个对象的概率),即