[編程珠璣筆記]第12章 取樣問題

整理了這一章提到的幾個算法,其中蓄水池算法書中沒有寫,這裏放在一起比較一下,出了方法2是C++的代碼,其它都是python的實現。

問題:
程序的輸入包括兩個整數m和n,其中m<n。輸出是0~n-1範圍內m個隨機整數的有序列表,不允許重複。從概率的角度說,我們希望得到沒有重複的有序選擇,其中每個選擇出現的概率相等。

1、以特定概率順序選擇每一個數
如果要從r個剩餘的整數中選出s個,則以s/r的概率選擇剩餘整數中的第一個整數,然後遞歸處理剩下的r-1個數。
select_num = m
remaining = n
for i in range(n):
if (randint() % remaining) < select
print i
select_num -= 1
remaining -= 1
這樣在原始數組[0, 1, 2, ... , n-1]中順序按規則選取,得到的結果直接就是有序的。
但是如果n比較大,例如生成一個隨機的32位整數序列,則n是最大的32位整數,這個程序就會比較慢。

時間:O(n)
空間:O(1)

2、每次隨機選取一個數,作爲一個被選中的數(經過查重)
用一個treeset記錄被選中的數,每次從整個候選空間中隨機出一個數,查看這個數是否已經在treeset中了,如果是則重新選擇,否則放入treeset。
void gensets(int m, int n){
set<int> S;
while (S.size() < m)
S.insert(bigrand() % n );
set<int>::iterator i;
for(i = S.begin(); i!=S.end(); ++i)
cout<< *i << endl;
}

時間:O(mlogm)
空間:O(m)
這個程序對於n遠大於m的情況,要比方法1塊很多。

3、將包含整數0~n-1的數組順序打亂,排序後輸出前m個
arr = [i for i in range(n)]
for i in range(n):
swap(arr[i], arr[randint(i,n-1)])

因爲每一次swap之後,前i個數就不再變了,所以可以只swap m次,for循環只需要range(m)
最後還需要對前m個數字排序,然後輸出

時間:O(m+mlogm)
空間:O(n)

4、蓄水池算法(和3有點類似)
首先構建一個可放k個元素的蓄水池,將序列的前k個元素放入蓄水池中。
然後從第k+1個元素開始,以k/i的概率來決定該第i個元素是否被替換到池子中。 當遍歷完所有元素之後,就可以得到隨機挑選出的k個元素。
根據本問題的要求,最後再對m個數進行一次排序。

for i in range(k+1, n):
M = randint(1,i)
if M < k:
swap(arr[M], arr[i])

時間:O(n + mlogm) [抽樣+排序]
空間:O(m)

5、優化
-當n爲100萬而m爲n-10時,即要選出絕大部分的數字,那麼可以先選出10個不包含的,然後輸出剩餘的。
-當m爲1000而n爲2^31時,可能可以先生成1100w個整數,然後排序並對其掃描已刪除重複的元素,最後得到1000w個有序元素的樣本。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章