有這樣一類問題, 就是大資料流中的隨機抽樣問題,即:
當記憶體無法載入全部資料時,如何從包含未知大小的資料流中隨機選取k個資料,並且要保證每個資料被抽取到的概率相等。這道題有兩個限制:
高效,即節省記憶體的使用
盡量隨機地返回值
假如我們去掉限制1,可以很簡單地做出來:我們將所有資料載入進記憶體,計算鍊錶長度,然後通過random函式來求取幾個隨機數。
但是這樣的效率並不高,把所有資料載入到記憶體,如果資料非常大可能會導致無法計算。
注意題目中有乙個說明,就是鍊錶。鍊錶這種資料結構是通過資料節點首尾相連形成的鏈式儲存結構。
既然是鍊錶,那麼可以乙個乙個節點處理,不需要將所有資料載入到記憶體,乙個節點乙個節點去處理,這還不夠形象,將題目換個形式來表述:
我們有大量的文字檔案存在硬碟中,想隨機抽取幾行,保證盡可能少得使用記憶體並且能夠完全隨機.之前想到的載入到記憶體就不太適合了,但是還可以想到別的辦法,比如每次讀取一行記錄載入到記憶體,記數+1,清空記憶體中行資料,直到最後統計一共多少行,然後根據總行數來計算k個隨機數。
如何再取回行對應的資料呢?我們可以再遍歷一遍,一邊遍歷一邊記錄這一行的行數是不是在k個隨機數中,如果是,則將該行內容保留。
這樣的話遍歷兩次應該可以做到,但是資料量大的時候遍歷兩次的時間消耗是非常高的。
所以還有更好的方案嗎,那就是水塘抽樣演算法。
對於複雜問題一定要學會歸納總結,即從小例子入手,然後分析,得出結論,然後在證明。不然遇到乙個抽象問題,不舉例感覺這個問題,直接解還是比較難的。
接下來從 k = 1 開始說明
k = 1
首先考慮最簡單的情況,當 k = 1 時,如何選取:
這樣就可以看出,對於k=1的情況,我們可以制定這樣簡單的抽樣策略:
在取第n個資料的時候,我們生成乙個0到1的隨機數p,如果p小於等於1/n,保留第n個數。大於1/n,繼續保留前面的數。直到資料流結束,返回此數。資料流中第 i 個數被保留的概率為 1/i 。只要採取這種策略,只需要遍歷一遍資料流就可以得到取樣值,並且保證所有數被選取的概率均為 1/n 。下面用數學歸納法證明此結論。
1) 當n=1時,第乙個元素以1/1的概率返回,符合條件。
2) 假設當n=k時成立,即每個元素都以1/k的概率返回,先證明n=k+1時,是否成立。
對於最後乙個元素顯然以1/k+1的概率返回,符合條件,對於前k個資料,被返回的概率為1/k * (1- 1/k+1)=1/k+1,滿足題意。
綜上所述,結論成立。
**
1ps: k=1 時**中生成的不是概率而是區間數的原因見 k>1 的策略。class
solution:
2def
__init__
(self, head: optional[listnode]):
3 self.head =head45
def getrandom(self) ->int:
6 node, i, ans = self.head, 1, 0
7while
node:
8if randrange(i) == 0: #
1/i 的概率選中(替換為答案)
9 ans =node.val
10 i += 1
11 node =node.next
12return ans
k >1
當 k>1時,即為水塘抽樣。
對於k>1的情況,我們可以採用類似的思考策略:
有了k =1 的理解,我們可以直接替換結論,只需把上面的 1/n 變為 k/n 即可。策略如下:
在取第n個資料的時候,我們生成乙個0到1的隨機數p,如果p小於等於 k/n,替換池中任意乙個為第n個數。大於k/n,繼續保留前面的數。直到資料流結束,返回此k個數。但是為了保證計算機計算分數額準確性,一般是生成乙個0到n的隨機數,跟k相比,道理是一樣的。可以以同樣的方法證明。
1)初始情況k<=n,出現在水庫中的k個元素的出現概率都是一致的,都是1
2)第一步。第一步就是指,處理第k+1個元素的情況。分兩種情況:元素全部都沒有被替換;其中某個元素被第k+1個元素替換掉。
我們先看情況2:第k+1個元素被選中的概率是k/(k+1)(根據公式k/i),所以這個新元素在水塘**現的概率就一定是k/(k+1)(不管它替換掉哪個元素,反正肯定它是以這個概率出現在水塘中)。
下面來看水塘中剩餘的元素出現的概率,也就是1-p(這個元素被替換掉的概率)。水塘中任意乙個元素被替換掉的概率是:(k/k+1)*(1/k)=1/(k+1),意即首先要第k+1個元素被選中,然後自己在集合的k個元素中被選中。
那它出現的概率就是1-1/(k+1)=k/(k+1)。可以看出來,舊元素和新元素出現的概率是相等的。
情況1:當元素全部都沒有替換掉的時候,每個元素的出現概率肯定是一樣的,這很顯然。但具體是多少呢?就是1-p(第k+1個元素被選中)=1-k/(k+1)=1/(k+1)。
3)歸納法:重複上面的過程,只要證明第i步到第i+1步,所有元素出現的概率是相等的即可。
**
1 vector reservoirsampling(vector& results, vector& nums, intk)21011
for (int i=k; ii) 16}
1718
return
results;
19 }
演算法 蓄水池抽樣
例題 有乙個機器按自然數序列的方式吐出球,1號球,2號球.現有乙個袋子,袋子裡最多只能裝下k個球,並且除袋子以外沒有更多的空間,球扔掉不能放回。設計一種選擇方式,使得當機器吐出第n號球時,袋子中的球數是k個,同時可以保證從1號球到n號球中的每乙個被選中進袋子的概率都是k n。具體例子 有乙個只能裝下...
蓄水池抽樣演算法
給你乙個長度為n的鍊錶。n很大,但你不知道n有多大。你的任務是從這n個元素中隨機取出k個元素。你只能遍歷這個鍊錶一次。你的演算法必須保證取出的元素恰好有k個,且它們是完全隨機的 出現概率均等 蓄水池抽樣演算法 該演算法是針對從乙個序列中隨機抽取不重複的k個數,保證每個數被抽取到的概率為k n這個問題...
蓄水池抽樣演算法
題目 要求從n個元素中隨機的抽取k個元素,其中n無法確定 解法 首先選擇n中的前k個數加入 蓄水池 中,然後從第k 1個數開始,以k k i i 1,2,3.的概率選擇這個數,然後在蓄水池中隨機選擇乙個數,並將其替換,n個元素遍歷完畢後,蓄水池中的k個數就是隨機選擇的。證明 這裡即需要證明每個數出現...