1、問題定義可以簡化如下:在不知道文件總行數的情況下,如何從文件中隨機的抽取一行,並且每行被抽中的概率相等?
首先想到的是我們做過類似的題目嗎?當然,在知道文件行數的情況下,我們可以很容易的用C運行庫的rand()函數隨機的獲得一個行數,從而隨機的取出一行,但是,當前的情況是不知道行數,這樣如何求呢?我們需要一個概念來幫助我們做出猜想,來使得對每一行取出的概率相等,也即隨機。這個概念即蓄水池抽樣(Reservoir Sampling)。
有了這個概念,我們便有了這樣一個解決方案:定義取出的行號為choice,第一次直接以第一行作為取出choice ,而后第二次以二分之一概率決定是否用第二行替換 choice ,第三次以三分之一的概率決定是否以第三行替換 choice ……,以此類推,可用偽代碼描述如下:
i = 0 while more input lines with probability 1.0/++i choice = this input line print choice
這種方法的巧妙之處在於成功的構造出了一種方式使得最后可以證明對每一行的取出概率都為1/n(其中n為當前掃描到的文件行數),換句話說對每一行取出的概率均相等,也即完成了隨機的選取。
具體操作可參考如下偽代碼:
Element RandomPick(file): Int count = 0; while(count <= file.size) If(random(0,count) == 0) picked = file[count]; ++ count; Return picked
證明如下:
2、可以對其進行擴展,即如何從未知或者很大樣本空間隨機地取k個數?
類比下即可得到答案,即先把前k個數放入蓄水池,對於第 i>=k+1,我們以 k/i 概率決定是否要把它換入蓄水池,換入時隨機的選取一個作為替換項,這樣一直做下去,對於任意的樣本空間n,對每個數的選取概率都為k/n。也就是說對每個數選取概率相等。
偽代碼:
init a reservoir with the size k add the first k elements into the reservoir for i = k+1 to N m = random(1,i); if(m < k) swap the m_th value and i_th value end for
數學證明:
一些等概率選取相關的題目:
1.等概率隨機排列數組(洗牌算法)
問題描述:假設有一個數組,包含n個元素。現在要重新排列這些元素,要求每個元素被放到任何一個位置的概率都相等(即1/n),並且直接在數組上重排(in place),不要生成新的數組。用 O(n) 時間、O(1) 輔助空間。
思路:先想想如果可以開辟另外一塊長度為n的輔助空間時該怎么處理,顯然只要對n個元素做n次(不放回的)隨機抽取就可以了。先從n個元素中任選一個,放入新空間的第一個位置,然后再從剩下的n-1個元素中任選一個,放入第二個位置,依此類推。按照同樣的方法,但這次不開辟新的存儲空間。第一次被選中的元素就要放入這個數組的第一個位置,但這個位置原來已經有別的(也可能就是這個)元素了,這時候只要把原來的元素跟被選中的元素互換一下就可以了。很容易就避免了輔助空間。
詳情:http://www.gocalf.com/blog/shuffle-algo.html
2.單次遍歷,等概率隨機選取問題
問題描述:假設我們有一堆數據(可能在一個鏈表里,也可能在文件里),數量未知。要求只遍歷一次這些數據,隨機選取其中的一個元素,任何一個元素被選到的概率相等。O(n)時間,O(1)輔助空間(n是數據總數,但事先不知道)。
詳情:http://www.gocalf.com/blog/random-selection.html
3.單次遍歷,帶權隨機選取問題
問題描述:有一組數量未知的數據,每個元素有非負權重。要求只遍歷一次,隨機選取其中的一個元素,任何一個元素被選到的概率與其權重成正比。
詳情:http://www.gocalf.com/blog/weighted-random-selection.html