最全隨機抽樣演算法 從N個數中抽取M個等 集合

2021-09-12 13:53:27 字數 4580 閱讀 5176

1.從n個數中等概率抽取m個數

從n個樣本中等概率抽取m個樣本(mpublic static setsampletest()

set.add(second);

while(set.contains(third))

set.add(third);

return set;

}public static void samplemassive()

}for(map.entryentry: map.entryset()) }1

2345

6789

1011

1213

1415

1617

1819

2021

2223

2425

2627

28上面的**是在24個數中隨機抽取3個數,然後將該抽樣重複一萬次,輸出最後的結果。

將samplemassive方法run起來以後,輸出結果如下:

0: 1218

1: 1239

2: 1195

3: 1200

4: 1213

5: 1282

6: 1297

7: 1241

8: 1200

9: 1270

10: 1272

11: 1277

12: 1250

13: 1270

14: 1233

15: 1212

16: 1298

17: 1228

18: 1238

19: 1212

20: 1209

21: 1308

22: 1308

23: 125612

3456

78910

1112

1314

1516

1718

1920

2122

2324

一共需要抽樣出來10000*3=30000個數,每個數出現的次數平均為30000/24=1250次。上面的結果大致滿足等概率均勻分布。

上面演算法的問題在於,當m比較大的時候,每次呼叫random方法生成的數與之前重合的概率也會越來越大,則while迴圈裡random的呼叫次數會越來越多,這樣時間複雜度就會公升高。

那麼具體的時間複雜度是多少呢?可以定量分析一下。

假設之前已經生成了x個數,接下來生成第x-1個數。

第一次呼叫random就成功生成第x-1個數的概率為1−xn 1 - \frac1− nx

​    

第二次呼叫random就成功生成第x-1個數的概率為(1−xn)xn (1 - \frac)\frac(1− nx

​    ) n

x​    

第k次呼叫random就成功生成第x-1個數的概率為(1−xn)(xn)k−1 (1 - \frac))}^(1− nx

​    

)( n

x​    

) k−1

那麼生成第x+1個數需要呼叫random方法的次數為:

e(x+1)=(1−xn)∗1+(1−xn)xn∗2+⋯+(1−xn)(xn)k−1∗k+⋯=nn−x e(x+1) = (1 - \frac) * 1 + (1 - \frac)\frac * 2 + \cdots + (1 - \frac))}^ * k + \cdots = \frac

e(x+1)=(1− nx

​    

)∗1+(1− nx

​    ) n

x​    

∗2+⋯+(1− nx

​    

)( n

x​    

) k−1

∗k+⋯= 

n−xn

​    

上述等差-等比數列求和的方法,見參考文獻1,只需要中學數學知識即可理解。

當m接近n時,此時時間複雜度接近o(nlogn) o(nlogn)o(nlogn),演算法的複雜度比較高。

上面的sample演算法比較笨,實現乙個通用的從n個數抽取m個的演算法。

public static setsampletest(int n, int m)

set.add(tmp);

}return set;}1

2345

6789

1011

1213

其中,n是原始樣本長度,m為待抽取樣本個數。

2.時間複雜度為o(n)的從n個數中抽取m個的演算法

上面的演算法,時間複雜度為o(nlgn) o(nlgn)o(nlgn)。那麼有沒有時間複雜度更低的演算法呢?

答案是有的,用蓄水池演算法就可以實現。

關於蓄水池演算法的具體原理,可查閱參考文獻2。

直接上乙個例子。

public static int reservoir(int array, int m) ;

int m = 2;

mapmap = new hashmap();

for(int i=0; i<10000; i++)

}for(map.entryentry: map.entryset()) }1

2345

6789

1011

1213

1415

1617

1819

2021

2223

2425

2627

2829

3031

上面**模擬的是從中隨機抽取兩個數,重複10000次。

最後執行的結果如下:

0: 4056

1: 4001

2: 3903

3: 4102

4: 393812

3453.隨機抽取有序列表

上面抽樣的結果都是無序的,只需要滿足最後出現的概率相等即可。例如從中抽取兩個數,有可能先抽到0,也有可能先抽到4。如果我們要求抽樣結果是有序的,那該怎麼辦?

這種情況在實際中很常見。比如在流量分配系統中,流量都是流式過來的,或者說是有序的。假設有十個流量依次過來,需要在這十個流量隨機選擇三個投放三個廣告,並且每個流量投放廣告的概率都相等。這種場景就跟抽取有序列表類似。

在knuth的《計算機程式設計藝術 第2卷 半數值演算法》一書中,給出了乙個演算法。

void generateknuth(int n,int m)

;int len = array.length;

listlist = new arraylist<>();

for(int i=0; ipublic static void massive_randomtest()

}for(map.entryentry: map.entryset()) }1

2345

6789

1011

1213

1415

1617

1819

2021

2223

2425

2627

上面**的寫法是按照演算法的思路來的。我在專案實現過程中,想了另外一種更容易理解,也更只管的實現方式。可以進行簡單的證明如下:

1.需要保證每個樣本被抽到的概率是mn \frac nm

​    

2.第乙個樣本按mn \frac nm

​    

的概率進行抽樣即可。

3.對於第二個樣本,如果第乙個樣本被抽中,其被抽中的概率為m−1n−1 \frac 

n−1m−1

​    

。如果第乙個樣本沒有被抽中,其被抽中的概率為mn−1 \frac 

n−1m

​    

。第二個樣本被抽中的概率為m−1n−1∗mn+mn−1∗(1−mn)=mn \frac * \frac + \frac * (1 - \frac) = \frac 

n−1m−1

​    ∗ n

m​    

+ n−1

m​    

∗(1− nm

​    

)= n

m​    

。4.對於第i個樣本,被抽中的概率為m−kn−i+1 \frac 

n−i+1

m−k​    

,其中k為前面已經抽中的個數,k<=m。即抽取第i個樣本時候,如果前面已經抽中了k個,那麼需要在剩下的n-i+1個樣本中抽取m-k個。

按照我自己理解的思路再實現一下,**更簡單,思路也更清晰一些:

public static listrandomtest() ;

int len = array.length;

listlist = new arraylist<>();

for(int i=0; i value)

}return list;

}public static void massive_randomtest()

}for(map.entryentry: map.entryset()) }1

2345

6789

1011

1213

1415

1617

1819

2021

2223

2425

2627

2829

最後的輸出結果為:

從輸入流中隨機抽取m個元素

有乙個很大很大的輸入流,大到沒有儲存器可以將其儲存下來,而且只輸入一次,如何從這個輸入流中隨機取得m個記錄。思路 我們可以用fisher yates隨機排列演算法解決該問題。用大小為m的陣列arr 0 m 1 來儲存隨機抽取的元素,arr 0 m 1 逐步初始化為輸入流的前m個元素的乙個隨機排列。對...

0 n 1個數中隨機選m個數

給定乙個n,乙個m,要求從0.n 1個數中隨機選取m個數。這裡參考 程式設計珠璣 中的乙個方法,既利用概率測試來進行選取。假設我們要從0到100中選取10個數。首先考慮0,我們選取它的概率為10 100 1 10,因此我們可以產生乙個隨機數 應該遠遠大於n 利用該數模100的值是否小於10來模擬選取...

從n個數中挑m個的排列組合演算法

created on 2020 7 16 從n個數中挑m個的排列組合演算法 var result 最終的結果 var array 1,2,3,4 被挑選的 param array 被選則的陣列 param max 要選出m個進行排列組合 param tmp 快取的陣列 function getone...