水塘抽樣算法

簡介

做用:水塘抽樣算法是一種抽樣算法,對於一個很大的集合,抽取的樣本值可以保證隨機.java

特色:其複雜度並不很高O(n),而且可以很大程度地節省內存.面試

問題導入

不少大公司的面試題都考察過這個算法,以谷歌爲例,有一道關於水塘抽樣的例題算法

我有一個長度爲N的鏈表,N的值很是大,我不清楚N的確切值.我怎樣能寫一個儘量高效地算法來返回K個徹底隨機的數.數組

這道題有兩個限制:數據結構

1.高效,即節省內存的使用dom

2:儘可能隨機地返回值函數

假如咱們去掉限制1,能夠很簡單地作出來,將全部數據加載進內存,計算鏈表長度,而後經過random函數來求取幾個隨機數.大數據

這樣的效率並不高,把全部數據加載到內存,若是數據很是大可能會致使沒法計算.spa

注意題目中有一個小tip,就是鏈表.鏈表這種數據結構是經過數據節點首尾相連造成的鏈式存儲結構.code

既然是鏈表,那麼能夠一個一個節點處理,不須要將全部數據加載到內存.一個節點一個節點去處理,這還不夠形象,將題目換個形式來表述:

咱們有1T的文本文件存在硬盤中,想隨機抽取幾行,保證儘量少得使用內存而且可以徹底隨機.

以前想到的加載到內存就不太適合了,可是還能夠想到別的辦法,好比每次讀取一行記錄加載到內存,記數+1,清空內存中行數據,直到最後統計一共多少行,而後根據總行數來計算K個隨機數.如何再取回行對應的數據呢?咱們能夠再遍歷一遍,一邊遍歷一邊記錄這一行的號碼是否是在k個隨機數中,若是是,則將該行內容保留.

這樣的話遍歷兩次應該能夠作到,可是1T數據遍歷兩次的時間消耗是很是高的.

因此還有更好的方案嗎,那就是水塘抽樣算法.

水塘抽樣算法實現

具體例子

咱們先從具體案例中理解水塘抽樣算法的實現,再從抽象的角度來理解.

假如10000個數,咱們要抽取十個隨機數.

一萬個數的樣本集合數組記做S.

十個隨機數的數組記做R,表明result.

先取數組S中前十個數填充進數組R.

算法的第一次迭代流程是這樣的:

  • 從第十一個數(下標爲10)開始迭代,生成一個0到10的隨機整數j,若是j<10(假如J=4),咱們就將數組R中的第5項(R[4])替換成S數組中的第11項(S[10]).

遍歷完成生成的R數組,就是咱們要求的隨機數組.

抽象概念

$ S[N] $記做:樣本集合

$ R[K] $記做:結果集合

$ N $記做:S數組大小

\(J\)記做:每次的隨機數

\(K\)記做:前K個隨機數

\(i\)記做:迭代次數.

步驟

  • \(S\)集合中前\(K\)個數填入\(R\)集合

  • \(S[K]\)開始遍歷

    生成隨機數\(J\),範圍是\(0->K+i-1\).由於數組下標從0開始,因此-1.

    若是\(J<K\),則替換\(R\)中的值->\(R[j] = S[i]\).

  • 遍歷結束,生成結果數組\(R\).

算法實現(JAVA)

int[] S = new int[10000];
        int N = S.length;
        Random random = new Random();
        //生成一萬個數的數組
        for (int r = 0;r < N; r ++){
            S[r] = random.nextInt(10000);
        }
        
        int k = 10;
        int[] R = new int[k];
        //S前K個數填充R數組
        for (int f = 0;f < k; f++){
            R[f] = S[f];
        }
        int j ;
        //遍歷數組S,根據算法,替換R數組中的元素,最終生成結果R數組.
        for (int i = k;i < S.length;i++){
            j = random.nextInt(i);
            if (j < k)  R[j] = S[i];
        }
        //打印R數組的結果
        for (int i =0;i < R.length;i++) {
            System.out.println(R[i]);
        }

總結一下這種算法.經過一遍遍歷就得到了K個隨機數,在很大數據的狀況下效率是很是高的,很是適合咱們的應用場景.

可是爲何這樣生成的數是徹底隨機的呢?

就剛纔的具體例子來說,第一次遍歷時,i=10,隨機數的範圍是0到10共11個數,那麼不替換的機率是\(10/11\),等到第二次迭代時,不替換的機率變成\(10/12\),第三次\(10/13\),第四次\(10/14\).......

這樣看來好像每一次的機率並不相等,其實並非這樣,咱們要看的是最終進入數組\(R\)中的機率,雖然第十一個數進入\(R\)的機率比較大,可是到最後他被替換的機率也很大,因此每一個數最終保留在\(R\)中的機率究竟是多少呢?

能夠參考一下維基百科中的證實,我以爲很是清晰.

在循環內第n行被抽取的機率爲k/n,以\(P_n\)表示。若是檔案共有N行,任意第n行(注意這裏n是序號,而不是總數).

被抽取的機率爲:

咱們能夠求得每行被抽取的機率是相同的,等於\(k/N\).

很是巧妙,因此當咱們面對這種情景時,能夠考慮使用水塘抽樣進行隨機抽取.

相關文章
相關標籤/搜索