洗牌算法与蓄水池抽样

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

洗牌算法

递归做法:先将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个对象的概率),即

洗牌算法与蓄水池抽样

时间: 2024-11-10 03:08:43

洗牌算法与蓄水池抽样的相关文章

算法之洗牌算法

洗牌算法是我们常见的随机问题,在玩游戏.随机排序时经常会碰到,一个最常用的地方就是组卷.它可以抽象成这样:M以内的所有自然数的随机顺序数组. package com.math; import java.util.Random; /** * @author summer * */ public class Shuffle { static final int[] a = new int[54]; static Random rnd = new Random(); static{ for(int i

完美洗牌算法

本文是看完july博客完美洗牌之后的个人笔记. 题目:把a1,a2,a3,a4,...,an-1 an,b1,b2,b3,...,bn-1,bn变成a1,b1,a2,b1,...,an,bn.要求时间复杂度为O(n),空间复杂度为O(1). 1.位置置换算法:b是新开的一个数组,但是时间复杂度为O(n),空间复杂度为O(n). void perfectShuttle(int a[],int n){ int n2=n*2,b[N]; for(int i=1;i<=n2;i++) b[(2*i)%(

洗牌算法Fisher-Yates以及C语言随机数的产生

前些天在蘑菇街的面试中碰到一道洗牌的算法题,拿出来和大家分享一下! 原题是:54张有序的牌,如何无序的发给3个人? 这个题是运用经典的洗牌算法完成.首先介绍一种经典的洗牌算法--Fisher-Yates.现在大家在网上看到,大多是Fisher-Yates算法的变形.将本来O(n2),简化到了O(n).代码如下: #include<stdio.h> #include <stdlib.h> void func(char *, int); void main() { char a[7]

洗牌算法(转载)

作者:flyinghearts出处:http://www.cnblogs.com/flyinghearts/ 又一次看到讨论"洗牌"算法的文章,奇怪不少人喜欢造轮子,但造的轮子却远没有STL的random_shuffle好用. 若某个序列里面的每个元素在每个位置已经等概率出现,那么新增加一个元素,只要新增加的元素和所有元素进行等概率交换,则新序列中每个元素在每个位置仍是等概率出现.(若原来有n个元素,新增加的第n+1个元素在任一位置b的概率显然是1/(n+1),在同一位置b,原来的n个

JS洗牌算法

洗牌算法 以请将1~10共10个数字的数组随机打乱为列子,目前我知道的有两种方法,一种sort()方法和push()方法. 一.sort方法: var arr=[1,2,3,4,5,6,7,8,9]; function Upset(a,b) { //用Math.random()函数生成0~1之间的随机数与0.5比较,返回-1或1   return Math.random()>.5 ? -1 : 1;  //简化上面的方法 // return 0.5 - Math.random();  } arr

洗牌算法浅试

Python有自带的洗牌算法函数shuffle(). 自己也通过学习也琢磨了一下它的实现,然后给出一个时间复杂度O(n),空间复杂度O(1)的例子: 1 import random 2 3 def shuffle1(lst) : 4 l = len(lst) 5 if l <= 1 : return lst 6 7 i = 0 8 while l > 1 : 9 j = int(random.random() * l) 10 t = lst[i] 11 lst[i] = lst[i+j] 12

转:浅谈洗牌算法(面试题)

很多人都有耳闻过洗牌算法,时常会在面试中碰到,我们下面来定义一下这个问题. 所谓洗牌算法,就是给你一个1到n的序列,让你随机打乱,保证每个数出现在任意一个位置的概率相同,也就是说在n!个的排列中,每一个排列出现的概率相同. 最朴素的做法 对于这个问题我们从最朴素的解法谈起.每次随机选出一个没有被选过的数放到一个队列中,如果随机出来的数已经被选过,那么继续随机直到遇到一个没有被选过的数放入到队列中:重复这样子操作直到所有的数都被选择出来. 我们看看这样子作为什么是对的.首先选第一个数的时候有n个数

随机算法之30万员工抽取10万员工(洗牌算法)

算法方面不是我的强项,所以遇到这个问题,记录一下解决方法: 最开始的时候,做法是从random中来随机选取一个,然后再判断已有列表中是否存在, 然后以此重复,看到这里,会算法的同学肯定会说这明显不行:就好比多少人中生日为一天的概率大于50%: 所以然后查找了一些资料,看到了洗牌算法:包括多种变种吧. 这里说一下我参考的解决办法: 比如有30万个元素的列表,那么第一次随机选择一个:记录位置是x: 然后把元素的最后一个位置元素存入位置x,然后把最后位置的元素删除: (这里在交换的时候,需要判断这个元

Javascript 洗牌算法,打乱数组,随机获取元素

//利用洗牌算法Array.prototype.shuffle=function(){ var i,t,m=this.length; while(m){ i=Math.floor(Math.random()*m--); t=this[m]; this[m]=this[i]; this[i]=t; } return this;} var arr=[1,2,3,4,5];console.log(arr.shuffle());console.log(arr.slice(0,2));