標籤:style os io strong for cti 代碼 amp line
對於n個樣本,如何均勻隨機的取出m個樣本?即n個樣本中每個樣本都能有m/n的機率被取中。
1.簡單插入取樣
這是最基本,最直觀的方法。在一個初始為空白的集合中插入1~n的隨機整數,知道個數為m個為止。但這個方法有個弱點,就是要插入一個數時,判斷集合中是否存在該數,如果其存在,則要繼續取樣直到取到一個不在原集合中的數,重複取樣需要很大的開銷,而且越到後來開銷越大。
2.Floyd取樣(將第n個數插入與前n-1個數的樣本的選取關聯起來)
Floyd取樣是大名鼎鼎的Robert W. Floyd提出來的。其基本思想:當已經在[1,n-1]個區間中已隨機取出m-1個樣本時,這時,產生一個1~n的隨機數,如果該數落在原來的m-1個樣本中,則樣本集合加入n;如果不落在原來的m-1個樣本中,那麼就將隨機數加入已取樣本集合。這樣的演算法複雜度為O(m)
證明:對於第n個數,其被選中的機率為1/n+(m-1)/n=m/n;
而對於前面n-1個數中的任意一個數,總被選中的機率P=上一輪被選中的機率+上一輪未被選中的機率*本輪被選中的總機率:
易得P=(m-1)/(n-1)*1+(n-m)/(n-1)*1/(n+1)=m/n.
遞迴實現:
- Function Sample(M,N) //編程珠璣第13章Floyd演算法
- if M = 0 then
- return the empty set;
- else
- S := Sample(M-1,N-1)
- T := RandInt(1,N)
- if T is not in S then
- insert T in S
- else
- insert N in S
- return
- 則把N插入到S中的機率為(M-1+1)/N = M/N;
- void floySampling(int n,int m) { if(m>n){ printf("Error\n"); return; } vector<int> q; for(int j=n-m;j<n;++j){ int t=rand()%(j+1); ++t; vector<int>::iterator t_position=find(q.begin(),q.end(),t); if(t_position==q.end()) q.push_back(t); else q.push_back(j+1); } for(vector<int>::iterator iter=q.begin();iter!=q.end();++iter) printf("%-3d",*iter); cout<<endl; }
3.有百度一到筆試題所聯想到的取樣方法
百度筆試原題: 為分析使用者行為,系統常需儲存使用者的一些query,但因query非常多,故系統不能全存,設系統每天只存m個query,現設計一個演算法,對使用者請求的query進行隨機播放m個,請給一個方案,使得每個query被抽中的機率相等,並分析之,注意:不到最後一刻,並不知使用者的總請求量。
答案策略:取一個[1,m+i]中的隨機數,如果隨機數落在(m,m+i]時,應該保留原來的m個數;如果隨機數落在[1,m]中,則應該用最新的一條記錄取代[1,m]中隨機的一個數。
證明如下:
1)假設現在系統讀取第n+1條記錄,現在儲存的m條記錄都是前面m+n條記錄中以m/(m+n)的機率留下來的;
2)取一個[1,m+n+1]的隨機數,按照上述策略。
3)現在新記錄能保留在m數組的機率為m/(m+n+1)
4)原來m數組中的數(設為A)在本輪選擇中還能保留的條件機率(條件是,上一輪選擇中,A被保留):
(n+1)/(m+n+1)+m/(m+n+1)*(1-1/m)=(m+n)/(m+n+1)。
然後要乘以其原來保留下的機率。得到的A仍在m數組中的機率為m/(m+n+1)。
如此迴圈,總是可以保障每個數被選擇的機率相等。該演算法的複雜度為O(n)
- void baiduSampling(int n,int m){if(m>n){printf("Error\n");return;}vector<int> v;for(int i=1;i<=m;++i){v.push_back(i);}for(int i=m+1;i<=n;++i){int t=rand()%(i-1);++t;if(t<=m)v[t-1]=i;}for(vector<int>::iterator iter=v.begin();iter!=v.end();++iter)printf("%-3d",*iter);cout<<endl;}
4.隨機順序序列
有時,我們不僅希望能在n個樣本中隨機選取m個樣本,還希望m個樣本的順序也是隨機的。顯然,簡單插入取樣最後得到的序列順序是隨機的。而後面兩種方法得到的順序並不是隨機的。Floyd每一輪插入樣本中,如果隨機數在原來的集合中,最大的數總是被插在容器的最後面,這是造成順序不隨機的主要原因。我們令其插入在隨機數的後面。以上只是直觀的上的說明。真正嚴謹的證明請看編程珠璣2的13章。
第三種方法的不隨機主要是因為初始的前m個樣本序列的不隨機造成的。
稍微修改一下floyd演算法代碼即可得到能產生隨機順序的序列的演算法:
- void generateRandomSeries(int n,int m){if(m>n){printf("Error\n");return;}deque<int> q;for(int j=n-m;j<n;++j){int t=rand()%(j+1);++t;deque<int>::iterator t_position=find(q.begin(),q.end(),t);if(t_position==q.end())q.push_front(t);elseq.insert(t_position+1,1,j+1);}for(deque<int>::iterator iter=q.begin();iter!=q.end();++iter)printf("%-3d",*iter);cout<<endl;}
等機率抽樣Floyd演算法(轉)