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...