隨機取樣問題的幾種方法比較

問題1:寫一個隨機洗牌函數。要求洗出的所有組合都是等概率的。 

這裏我們的答案:都假定數組從0開始:

1. 參考Cracking the coding interview--答案20.2

void RandomShuffle1(int a[], int n){
    for(int i=0; i<n-1; ++i){
        int j = rand() % (n-i) + i; // 產生i到n-1間的隨機數
        Swap(a[i], a[j]);
    }
}

2. STL中用的random_shuffle算法:參考侯捷STL源碼剖析

void RandomShuffle2(int a[], int n){
    for(int i=1; i<n; ++i){
        int j = rand() % (i+1) ; // 產生0到i間的隨機數
        Swap(a[i], a[j]);
    }
}

3. 從尾到頭類似2:

void RandomShuffle3(int a[], int n){
 for (int i=n-1; i>0; --i) 
    { 
        int j = rand()%(i+1);  // 產生0到i間的隨機數
        swap(a[j],a[j]); 
    } 
}

4 從尾到頭類似1

void RandomShuffle4(int a[], int n){
 for (int i=n-2; i>=0; --i) 
    { 
        int j =  rand() % (n-i) + i; // 產生i到n-1間的隨機數
        swap(a[j],a[j]); 
    } 
}

可以考慮爲什麼STL中選取的是第二種方法呢?


問題2:隨機地從大小爲n的數組中選取m個元素出來,然後輸出。要求每個元素被選中的概率都相等。

假設有一個5維數組:1,2,3,4,5。如果第1次隨機取到的數是4, 那麼我們希望參與第2次隨機選取的只有1,2,3,5。既然4已經不用, 我們可以把它和1交換,第2次就只需要從後面4位(2,3,1,5)中隨機選取即可。同理, 第2次隨機選取的元素和數組中第2個元素交換,然後再從後面3個元素中隨機選取元素, 依次類推。

1. 參見編程珠璣

void getRandNumber1(int A[], int m, int n)
{
	srand(time(NULL));
	int i;
	for(i=0;i<n;++i)
	{		
		if( rand()%(n-i) < m)
		{
			printf("%d  ",A[i]);
			m--;
		}
	} 	
}
2. 利用set中的元素不重複性:參見編程珠璣

void getRandNumber2(int A[], int m,int n)
{
	srand(time(NULL));
	set<int> S;
	while(S.size()<m)   //直到填滿 
		S.insert(A[rand()%n]);
	set<int>::iterator i;
	for(i=S.begin();i!=S.end();++i)
		cout<<*i<<" "; 
}

當m接近n時,該算法要丟掉很多隨機數,因爲它們之前已經在集合中,故修改成下面算法,最壞情況下也只用m個隨機數。

Floyed 基於集合的算法:參考編程珠璣

void getSet(int A[], int m,int n)
{
    srand(time(NULL));
    set<int> S;
    for(int i=n-m;i<n;++i)
    {
        int t=rand()%(i+1);
        if(S.find(t) == S.end())
                S.insert(A[t]);
        else
                S.insert(A[i]);
    } 
    set<int>::iterator j;
    for(j=S.begin();j!=S.end();++j)
        cout<<*j<<" "; 
}

3. 參考Cracking the coding interview--答案20.3

void getRandNumber3(int A[], int m, int n)
{
       srand(time(NULL));
	for(int i=0;i<m;++i)
	{
		int j = rand()%(n-i)+i;  //產生i-n-1的隨機數
		swap(A[i],A[j]);
	}
	for(i=0;i<m;++i)
		cout<<A[i]<<" ";
}

4. 蓄水池抽樣算法:

void getRandNumber4(int A[], int m, int n)
{
       srand(time(NULL));
	for(int i=m;i<n;++i)
	{
		int j = rand()%(i+1)  //產生0-i的隨機數
		if( j<m)
			swap(A[i],A[j]);
	}
	for(i=0;i<m;++i)
		cout<<A[i]<<" ";
}

先把前m個元素放入蓄水池,對第m+1個元素,我們以m/(m+1)概率決定是否要把它換入蓄水池,換入時隨機的選取一個作爲替換項,這樣一直做下去,對於任意的樣本空間n,對每個元素的選取概率都爲m/n,也就是說對每個元素選取概率相等。

當n未知的時候,算法4同樣能處理,這是前面三個算法無法做到的。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章