等概率無重複的從n個數中選取m個數

2021-08-31 18:21:21 字數 3850 閱讀 9810

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!

問題描述:程式的輸入包含兩個整數m和n,其中m學習過概率統計的同學應該都知道每乙個數字被抽取的概率都應該為m/n. 那麼我們怎麼構造出這樣的概率呢?在《程式設計珠璣》上面是這樣解析的:

依次考慮整數0,1,2,.....,n-1,並通過乙個適當的隨機測試對每個整數進行選擇。通過按序訪問整數,我們可以保證輸出結果是有序的。 假如我們考慮m = 2,n = 5的情況,那麼選擇的每乙個數字的概率都應該是2/5,我們怎麼樣才能做到呢?不慌張,慢慢來。

下面給出我的分析過程:在0,1,2,3,4這五個數字中,我們依次對每乙個數進行分析,第一次遇到0時,它的選擇概率應該是2/5,如果選中了,我們開始測試第二個數1,這個時候因為1選中了,所以1這個數字的選中概率就變小了,變成1/4了,有人說這似乎不對吧,因為題目說讓每乙個數字選中的概率是一樣大的,而現在?乙個2/5,乙個1/4,這怎麼行呢?其實不是這樣的,認真思考一下就知道了,數字1選中的概率等於什麼? 數字1選中的概率p(1) = 數字0選中的概率 * (1/4) + 陣列0沒選中的概率*(2/4)這樣推算下 (2/5 * 1/4) + (3/5 * 2/4) = 8/20 = 2/5 。這不就一樣了嗎?呵呵!下面給出來自knuth的《the art of computer programming, volume2:seminumerical algorithms》的偽**:

select = mremaining = nfor i = [0,n)     if (rand() % remaining) < select             print  i             select --     remaining--

int

gen(int m,int n)

remaining--;    }    return

0;}

可以優化為這樣:

int

genknuth

(int m,int n)

return

0;}

**很精簡,**遵守的規則應該是要從r個剩餘的整數中選出s個,我們以概率s/r選擇下乙個數。這個概率的選擇方式和我們上面證明的是一樣的。所以在程式結束的時候一定會列印出m個數字,且每乙個數字的被選擇概率相同,為m/n。首先是乙個迴圈,這個迴圈確保了輸出的數是不重複的,因為每次的i都不一樣

其次是m個數,在每次迴圈中都會用rand()%(n-i)再次是如何保證這m個數是等概率取到的

在第一次迴圈中i=0, n-i=n, 則隨機數生成的是0-n-1之間的隨機數,那麼此刻0被取到的概率為 m/n-1

在第二次迴圈中i=1,n-i=n-1,則隨機數生成的是0-n-2之間的隨機數,這時1被取到的概率就和上一次迴圈中0有沒有取到有關係了。假設在上一次迴圈中,沒有取,則這次取到的1的概率為 m/n-2;假設上一次迴圈中,已經取到了,那麼這次取到1的概率為m-1/n-2,所以總體上這次被取到的概率為 (1-m/n-1)*(m/n-2)+(m/n-1)*(m-1/n-2),最後通分合併之後的結果為m/n-1和第一次的概率一樣的

同理,在第i次迴圈中,i被取上的概率也為m/n-1

2、等概率順序取資料的第二種方法,可以使用集合的思想

由於集合元素不重複,如果按等概率選擇乙個隨機數,不在集合中就把它插入,反之直接拋棄,直到集合元素個數達到m個,同樣可以滿足要求,並且用c++的stl很容易實現:

void

gensets

(int m,int n)

這個演算法的主要問題是,如果拋棄已存在的元素的次數過多,相當於多次產生隨機數並進行集合操作,效能將明顯下降。比如當n=100而m=99,取第99個元素時,演算法「閉著眼睛亂猜整數,直到偶然碰上正確的那個為止」(《程式設計珠璣(續)》,13.1節)。雖然這種情況會在「從一般到特殊」提供解決方案,但下面的floyd演算法明顯規避了產生隨機數超過m次的問題。

習題12.9提供了一種基於stl集合的隨機數取樣方法,可以在最壞情況下也只產生m個隨機數:限定當前從中取值的區間的大小,每當產生重複的隨機數,就把這一次迭代時不會產生的第乙個隨機數拿來替換。

int

genfloyd

(int m,int n)

for(i=s.begin();i!=s.end();++i)        cout

<

從「打亂順序」出發

int

genshuf

(int m,int n)

//sort(x,x+m);

//sort是為了按序輸出

for(i=0;icout

當然了,這個題目還有其他的解法,這是在網上看到的其他的解法。他們將這樣的問題抽象的定義為蓄水池抽樣問題。其思路是這樣的,先把前k個數放入蓄水池中,對第k+1,我們以k/(k+1)的概率決定是否要把它換入蓄水池,換入時我們可以隨機挑選乙個作為替換位置,這樣一直到樣本空間n遍歷完,最後蓄水池中留下的就是結果。這樣的方法得到的結果也是正確的,且每乙個數字被選擇的概率也是k/n。

這個問題其實還可以擴充套件一下:

如何從n個物件(可以以此看到這n個物件,但事先不知道n的值)中隨機選擇乙個?比如在不知道乙個文字中有多少行,在這樣的情況下要求你隨機選擇檔案中一行,且要求檔案的每一行被選擇的概率相同。 在知道n這個總物件個數的情況下,誰都知道概率是1/n. 但是我們現在不知道,怎麼辦呢?

考慮這樣是不是可以,我們總是以1/i的概率去選擇每一次遍歷的物件,比如從1,2,3,4,5,6,....,n, 每一次遍歷到x時,總是以1/x的概率去選擇它.

整體思路如下:

我們總選擇第乙個數字(文字行),並以概率1/2選擇第二個(行),以1/3選擇第三行,也就是說設結果為result,遍歷第乙個時result = 1,第二個時以1/2的概率替讓result = 2,這樣一直遍歷概率性的替換下去,最終的result就是你的結果。他被選擇的概率就是1/n。

證明思路如下:

第x個數被選擇的概率等於x被選擇的概率 * (x+1沒被選擇的概率) * (x+2沒有被選擇的概率) *......*(n沒有被選擇的概率)  具體化一下

2被選擇的概率 = 1/2  * 2/3 * 3/4 * 4/5 .....* (n-1/n) 我想你知道答案了吧? 對! 是1/n.這樣就可以在不知道n的大小的情況下等概率的去選擇任意乙個物件了!

i = 0

whilemore input lines

with probability 1.0/++i

choice =thisinput line

print choice

init : a reservoir with the size: k                        for   i= k+1 to n                              m=random(1, i);                              if( m < k)                                      swap the mth value and ith value                        end for
參考

給我老師的人工智慧教程打call!

從m個數中選擇n個數的實現

從m個數中選出n個數來 0 n m 要求n個數之間不能有重複,其和等於乙個定值k。求一段程式,羅列所有的可能。例如備選的數字是 11,18,12,1,2,20,8,10,7,6 和k等於 18 那麼組合的可能有 18 8,10 2,20 12,6 11,7 11,1,6 1,10,7 12,2,8 ...

遞迴實現 從n個數中選取m個數的所有組合

有n n 0 個數,從中選取m n m 0 個數,找出所有的組合情況 不分順序 這樣的組合共有 cm n n n 1 n m 1 m 乙個陣列 data 有 n 個元素,從中選取 m 個數的組合 arr,使用遞迴演算法實現是這樣乙個過程 1 選擇 data的第1個元素為arr的第乙個元素,即 arr...

輸出從n個數中選m個數的所有組合

題目 n個數1,2,n,從這n個數中任意選m個數,輸出所有不同組合,共有c n,m 種不同組合。如n 5,m 3,會產生如下輸出 5 4 3 5 4 2 5 4 1 5 3 2 5 3 1 5 2 1 4 3 2 4 3 1 4 2 1 3 2 1 解題思路1 採用遞迴的方法,終止條件是當m 0時,...