【C/C++】隨機數問題
最初問題:從n個數中隨機選擇m個數(0<=m<=n)。
為了便於描述,可以將該問題抽象為:從0-n-1這n個數中隨機選擇m個數。計算機能夠提供的隨機數都是偽隨機的,我們假設計算機提供的偽隨機數為真正的隨機。
0、產生一個隨機數
系統(c/c++)提供的rand函式只有15位,如果不滿足要求,需要自己擴充套件,30位的隨機函式如下:
[cpp] view plaincopyprint?- /** @brief 返回一個30bit的隨機數
- ** @note 系統自帶的rand只有15bit
- */
- int BigRand()
- {
- static bool flag=false;
- if(flag==false)
- {
- srand(time(0));
- flag = true;
- }
- return (rand()<<15)+rand();
- }
1、最簡單的解法
每次產生一個0-n-1之間的隨機數,放入一個集合中,直到集合的大小為m。C++的STL中有set,比較方便:
[cpp] view plaincopyprint?- void GetRandNum_set(int m,int n)
- {
- cout<<__FUNCTION__<<": ";
- set<int> s;
- while(signed(s.size())<m)
- {
- s.insert(RandInt(0,n-1));
- }
- set<int>::iterator i=s.begin();
- while(i!=s.end())
- cout<<*i++<<" ";
- cout<<endl;
- }
上面的程式碼工作沒有問題,但是當m接近n且很大時,最後幾個數的產生將會很困難。因為會生成大量的重複的數。
如何不產生重複的數呢?
2、最多n次的解法
假設當前剩餘m個數要選,
從0開始到n-1這n個數,以m/n的概率選中選中0:總共n個數,要選出m個;
對於1:如果選中0,則以(m-1)/(n-1)的概率選擇1(總共n-1個,要選m-1個);如果沒選中,則以m/(n-1)的概率選(總共n-1個,要選m個);
……
對於i:總共還剩下n-i個,還需要選m個,那麼選中的概率就是m/(n-i)。
沒選中一個,剩餘要選的數就減少一個。
因此程式碼如下:
[cpp] view plaincopyprint?- /** @brief 在[0-n)中隨機的選擇m個不同的數
- ** 並按序輸出
- */
- void GetRandNumSorted(int m,int n)
- {
- cout<<__FUNCTION__<<": ";
- if(m<0 || m>=n) return;
- for(int i=0; m!=0 && i<n; i++)
- {
- if(BigRand()%(n-i)<m)
- {
- cout<<i<<" ";
- m--;
- }
- }
- cout<<endl;
- }
顯然,這時輸出是從小到大按序選擇的。
其中:if(BigRand()%(n-i)<m) 的概率為:m/(n-i)。
可以分析,每個數選中的概率都是m/n:
數 選中概率
0: m/n
1: m/n * (m-1)/(n-1) + (1-m/n) * m/(n-1) =m/n;
2: 好多項相加,這裡就不寫了。。。
……
3、不按序輸出
如果要求不按序輸出,有兩種解決辦法。
一種是將上面的結果儲存起來,然後再打亂儲存的陣列。
還有一種就是直接產生m個隨機數。
先看直接產生m個隨機數,其實就是先從0-n-1中隨機選擇一個,作為第一個;然後再從剩下的n-1個數中隨機選擇一個作為第二個……直到選出第m個。這就是所謂“完美洗牌”或者打亂陣列。
[cpp] view plaincopyprint?- /** @brief 在[0-n)中隨機的選擇m個不同的數
- ** 並隨機輸出
- */
- void GetRandNum(int m, int n)
- {
- cout<<__FUNCTION__<<": ";
- int * p= (int*)malloc(sizeof(int)*n);//!!!
- for(int i=0;i<n;i++)
- p[i] = i;
- ///shuffle p[0...m-1]
- for(int i=0; i<m; i++)
- {
- swap(p[i],p[RandInt(i,n-1)]);
- cout<<p[i]<<" ";
- }
- cout<<endl;
- free(p);
- }
這裡需要一個函式,能夠隨機產生一定範圍內的數:
- /** @brief 返回[l,u]之間的一個隨機數 **/
- int RandInt(int l, int u)
- {
- l = l<u?l:u;
- u = l<u?u:l;
- return BigRand()%(u-l+1) + l;
- }
這種演算法的問題是,如果n很大,m很小,對輔助空間的浪費太嚴重。因為開闢了那麼大的空間,實質只用了很少一部分。
另一種就是先按序隨機選擇m個數,然後再打亂:
[cpp] view plaincopyprint?- /** @brief 在[0-n)中隨機的選擇m個不同的數
- ** 並隨機輸出
- */
- void GetRandNum2(int m, int n)
- {
- cout<<__FUNCTION__<<": ";
- int * p= (int*)malloc(sizeof(int)*m);
- int tm=m;
- for(int i=0,j=0; m!=0 && i<n; i++)
- {
- if(BigRand()%(n-i)<m)
- {
- p[j++]=i;//cout<<i<<" ";
- m--;
- }
- }
- for(int i=0; i<tm; i++)
- {
- swap(p[i],p[RandInt(i,tm-1)]);
- cout<<p[i]<<" ";
- }
- cout<<endl;
- free(p);
- }
4、隨機讀取檔案中的一行
在不知道檔案總行數的情況下,隨機讀取檔案中的一行。
最直觀的做法就是,先讀取一次檔案,確定總行數n。然後產生一個1-n的隨機數m,再讀取第m行。顯然這是可行的,但是問題是如果檔案很大,平均要遍歷檔案1.5次。效率很低。
而且如果檔案在不算增長,那麼這個方法就不行了。
通過上面的演算法的啟發,其實也可以只讀取一次。
首先讀取第一行,如果只有一行,就結束了,設為line;
如果有第2行,那麼以1/2的概率替換line;這時1、2兩行被選中的概率都是1/2.
如果有第3行,那麼以1/3的概率替line;則第3行被選中的概率是1/3,1、2兩行被選中的概率則都是1/2*2/3=1/3.
……
第i行,以1/i的概率替換line。
直到檔案結束。
[cpp] view plaincopyprint?- /** @brief 從檔案fname中隨機讀取一行 */
- void GetOneLineRand(const char *fname)
- {
- cout<<__FUNCTION__<<": ";
- string line,str_save;
- ifstream ins(fname);
- int cnt=1;
- while(getline(ins,line))
- {
- if(cnt==1)
- {
- str_save = line;
- }
- else
- {
- if(RandInt(1,cnt)==1)///[1,cnt]
- str_save = line;
- }
- cout<<cnt<<" : "<<line<<endl;
- cnt++;
- }
- cout<<"rand line : "<<str_save<<endl;
- ins.close();
- }
這裡的if(RandInt(1,cnt)==1)裡的1,可以是[1,cnt]中任意一個值,概率均為1/cnt。
5、隨機讀取k行
先去讀k行,儲存在一個數組中(假設檔案至少有k行);
然後每讀取一行,都以k/n的概率替換陣列中的任意一行,其中n為當前總共讀取的行數。
[cpp] view plaincopyprint?- /** @brief 從檔案fname中隨機讀取k行
- */
- void GetRandLines(const char *fname, int k)
- {
- cout<<__FUNCTION__<<": ";
- string * kstr = new string[k], line;
- ifstream ins(fname);
- int cnt=1;
- while(cnt<=k)///先讀取前k行
- {
- if(getline(ins,kstr[cnt-1])) cnt++;
- else break;///檔案沒有k行,直接退出
- }
- while(getline(ins,line))
- {
- if(RandInt(1,cnt)<=k)/// p=k/cnt
- {
- swap(kstr[RandInt(1,k)-1],line);///隨機替換一行
- }
- cnt++;
- }
- for(int i=0; i<k ;i++)
- {
- cout<<kstr[i]<<endl;
- }
- cout<<endl;
- delete[] kstr;
- ins.close();
- }
其他問題請參考《程式設計珠璣-第12章》。
相關推薦
.Net基礎【擴展】隨機數
進行 body rup 數據 為我 nbsp gpo string his 隨機數的定義為:產生的所有數字毫無關系. 1.隨機數的原理: 線性同余法:第n+1個數=(第n個數*29+37) % 1000 編寫一個自己的隨機數類: class MyRand { pr
【C++】隨機數引擎
【c++】 style ble engine 如果 技術 efault 生成 distrib rand() 基本:使用隨機數時,經常見到的是C標準庫提供的函數rand(),這個函數會生成一個0到RAND_MAX之間的一個整形數; 分布:為了得到一個給定範圍內的隨機數,通常
【C++】隨機數rand( ) 和 隨機數引擎
rand() 基本:使用隨機數時,經常見到的是C標準庫提供的函式rand(),這個函式會生成一個0到RAND_MAX(32767)之間的一個整形數; 分佈:為了得到一個給定範圍內的隨機數,通常會對生成的隨機數取餘:rand()%n,rand()%(n-m)+m; 種子:通過
【C語言】統計隨機數中數字出現個數,並列印直方圖
實現功能:生成二十個隨機數。統計二十個數中,0-9數字出現的次數,並列印成直方圖 #include<stdio.h> #include<stdlib.h> #define N 20 int a[N],b[10]; void gen_rand
【原創】開源Math.NET基礎數學類庫使用(13)C#實現其他隨機數生成器
1 public abstract class RandomSource : System.Random 2 { 3 readonly bool _threadSafe; 4 readonly object _lock = new objec
【C/C++】隨機數問題
最初問題:從n個數中隨機選擇m個數(0<=m<=n)。 為了便於描述,可以將該問題抽象為:從0-n-1這n個數中隨機選擇m個數。計算機能夠提供的隨機數都是偽隨機的,我們假設計算機提供的偽隨機數為真正的隨機。 0、產生一個隨機數 系統(c/c++)提供的rand函式只有15位,如果不滿足要
【基礎】隨機數生成--C++原始碼(VS2015)
#include <iostream> #include <ctime> #include <vector> using namespace std; void Print(const vector<int> &vec
【學習拓展】C語言 隨機數應用:偽隨機機制
一、總論 1.偽隨機機制的意義 1.1 什麼是偽隨機機制 日常生活中大家都喜歡玩單機或者網路遊戲,而在這些遊戲中常常會存在隨機,比如暴擊率20%或者被動擊暈15%等等,而在這些隨機事件中,我們往往被告知這些隨機事件也能分成兩類:一類是固定的概率,常被稱
【C語言】統計數字在排序數組中出現的次數
語言 個數 統計 ret r+ () class tdi times //數字在排序數組中出現的次數。 //統計一個數字在排序數組中出現的次數。比如:排序數組{1,2,3,3,3,3,4,5}和數字3,因為3出現了4次,因此輸出4. #include <stdio
【BZOJ1935/4822】[Shoi2007]Tree 園丁的煩惱/[Cqoi2017]老C的任務 樹狀數組
tchar get ont n+1 div 區域 spa 都是 struct 題意:兩道題差不多,都是給你一堆平面上的點,每個點有權值,然後m次詢問求某一矩形區域內的點權和 題解:先離散化,然後將詢問拆成左右兩條線段,然後將點和這些線段一起按x坐標排序,在y軸上維護樹狀數
【動態規劃】 Codeforces Round #416 (Div. 2) C. Vladik and Memorable Trip
and main spa def esp 動態 return 價值 can 劃分那個序列,沒必要完全覆蓋原序列。對於劃分出來的每個序列,對於某個值v,要麽全都在該序列,要麽全都不在該序列。 一個序列的價值是所有不同的值的異或和。整個的價值是所有劃分出來的序列的價值之和。
【動態規劃】Codeforces Round #406 (Div. 2) C.Berzerk
[1] space node sca 一個 for 隊列 ber 動態規劃 有向圖博弈問題。 能轉移到一個必敗態的就是必勝態。 能轉移到的全是必勝態的就是必敗態。 轉移的時候可以用隊列維護。 可以看這個 http://www.cnblogs.com/quintessence
【C++ STL】Deques
容器 ever pty ngs 速度 pos algo dom 器) 1、結構 容器deque和vector非常相似,也是采用動態數組來管理元素,提供隨機存取,有著和vector幾乎一樣的接口,不同的是deque的動態數組頭尾都開放,因此可以在頭尾都可以進行快速的安插和
【C++ STL】容器的選擇
但是 函數 pair list 成員 cto 允許 數據 結構 c++提供了各具特長的容器,那麽我們該如何選擇最佳的容器? 缺省狀態下應該選擇vector,因為vector內部結構最簡單,並允許隨機存取,所以數據的存取十分方便,數據的處理也快。 如果經常要在頭部和尾部安插
【C++ STL】Queue
stack push com col 第一個 順序 size deque lis 1、定義 class queue<>實作為一個queue(也成為FIFO,先進先出)。可以使用push()將任意數量的元素置入queue中,也可以使用pop()將元素以其插入順
【2017-06-20】Linux應用開發工程師C/C++面試問題之一:Linux多線程程序的同步問題
依次 其它 如果 開發工程師 logs 特殊 另一個 特殊情況 發生 參考之一:Linux 線程同步的三種方法 鏈接地址:http://www.cnblogs.com/eleclsc/p/5838790.html 簡要回答: Linux下線程同步最常用的三種方法就是互斥鎖、
【Appnium+C#+Winform自動化測試系列】一、獲取本機連接的設備、啟動多個Appnium和獲取本機啟動的Appnium
net 系列 () 定向 目的 res listening toa 路徑 本系列內容,準備根據所完成的項目為基線,一步一步的把整個設計和實現過程梳理。 先從基本的一些環境問題入手,梳理清楚關於手機設備和Appnium。因為我們在後面的建立Appnium連接時,需要
【機房收費系統C#版】——導出Excel
missing watermark columns 感覺 end 巨人 orm content file 前言 機房合作開始好長了一段時間。反重復復開了幾次會,項目也是一拖再拖,作為組長。有80%的責任都在於我。為了不讓這個項目陪著我過春節。要求自己
【入門篇】Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署發布
框架 logs ice term con scrip 什麽 5.1 cal 由於最近工作的需要,本人學習了一下利用高性能web server - Nginx,來發布C/C++編寫的fastCGI程序,詳細細節如下。 1.介紹 Nginx - 高性能w
【網易】 【作業】 程序設計入門—C語言 翁愷 第二周
rate span asio tin bar ase read con hab #include<stdio.h> int main() { int a=0,b=0; scanf("%d",&a); if(a>=800)