Rand5生成等機率Rand3html
這個題目能夠擴展爲:利用等機率RandM生成等機率RandN (M > N)數組
這裏,咱們首先明白一個簡單的知識點:若是 randN 能夠等機率生成 [0, N) 之間的數dom
假設 N = T的整倍數,那麼咱們能夠直接使用 RandN() / (N / T) 來等機率生成 [0, T)之間的數函數
好比說,Rand21能夠等機率生成[0, 21)之間的數,那麼 Rand21() / 3 就能夠等機率生成 [0, 7)之間的數,即 實現了 Rand7函數優化
// 利用 Rand5 實現 Rand3 int Rand3() { int tmp = Rand5(); while (tmp >= 3) { tmp = Rand5(); } return tmp; }
證實:以輸出0爲例,看看機率是多少。ui
x的第一個有效數值是經過Rand5獲得的。Rand5返回0的機率是1/5,若是這事兒發生了,咱們就獲得了0,不然只有當Rand5返回3或4的時候,咱們纔有機會再次調用它來獲得新的數據。spa
第二次調用Rand5以後,又是有1/5的機率獲得0,2/5的機率獲得3或4致使循環繼續執行下去,如此反覆。code
所以機率的計算公式爲:htm
優化一下:咱們在開始就說明了一個常識,這裏,若是RandM 等機率 生成 RandN 時,當 M 很大 而 N 很小時,這時不少狀況是在while裏循環,可能一直循環blog
所以,我麼把while循環的邊界調整一下
int RandN() { int t = N; // t是小於M的,N的最大倍數 while (t + N < M) { t = t + N; } int tmp = RandM(); while (tmp >= t) { tmp = RandM(); } return tmp / (t / N); }
這個題目能夠擴展爲:利用等機率RandM生成等機率RandN (M < N)
這裏,咱們要首先 說明一個知識點:
咱們能夠利用 等機率RandM函數 生成 等機率 Rand(M * M)
好比說:M = 5
step1. Rand5 能夠等機率生成 0, 1, 2, 3, 4 (1/5機率) step2. 設 上一步Rand5的結果爲t,則 mul = M * t 等機率生成:0,5,10,15,20 (1/5機率) step3. 再次調用 Rand5 等機率生成 0, 1, 2, 3, 4 ,設結果爲 s (1/5機率)
step4. 將step2 和 step3的兩個結果相加, 就能夠等機率生成 0,1,2...24 (相乘事件,1/25機率)
這樣就實現了 Rand25
由於這一題中M < N, 而咱們能夠直接利用 RandM 來 等機率生成 Rand(M * M) ,這時 M * M 極可能就大於N了,若是不大於N,那麼再次 翻倍便可
假設 M * M 大於 N,聯想到第一題,相信你們很容易給出第二題的解了
int RandN() { int t = N; // t是小於M×M的,N的最大倍數 while (t + N < M * M) { t = t + N; } int tmp = RandM() * M + RandM(); // 等機率生成 Rand(M*M) while (tmp >= t) { tmp = RandM(); } return tmp; }
問題描述:假設咱們有一堆數據(可能在一個單鏈表裏,也可能在文件裏),數量未知。
要求只遍歷一次這些數據,隨機選取其中的一個元素,任何一個元素被選到的機率相等。
O(n)時間,O(1)輔助空間(n是數據總數,但事先不知道)
解題思路:若是元素總數爲n,那麼每一個元素被選到的機率應該是1/n。然而n只有在遍歷結束的時候才能知道,在遍歷的過程當中,n的值還不知道,能夠利用乘法規則來逐漸湊出這個機率值
void GetRandomSingleLine(FILE* fp, char* randomLine) { srand(time(NULL)); int i = 1; char tmp[MAX_LINE]; while (fgets(tmp, sizeof(tmp), fp) != NULL) { if (rand() % i == 0) { // rand() % i == 0 的機率爲 1/i strcpy(randomLine, tmp); } ++i; } }
當文件行數爲1時,rand() % 1確定爲0,直接輸出
當文件行數爲2時,rand() % 2 等於0 的機率爲 1/2,此時 輸出第一行 和 第二行 的機率均爲 1/ 2
當文件行數爲3時:
輸出第一行的機率是:1 * (1 - 1/2) * (1 - 1/3) = 1/3
輸出第二行的機率是:1 * (1/2) * (1 - 1/3) = 1/3
輸出第三行的機率是:1 - (1/3 + 1/3) = 1/3 (注意:題目要求必須輸出一行)
解題思路:先讀入第 1 ~ k 行保存,之後每次讀入第 i 行,都以 k / i 的機率把剛讀入的一行隨機替換以前保存的 k 行中的一行。
void GetRandomLines(FILE* fp, char** randomLine, int k) { srand(time(NULL)); char tmp[MAX_LINE]; // 讀入文件第1到k行 for (int i = 0; i < k; ++i) { fgets(randomLine[i], MAX_LINE, fp); } int i = k + 1; while (fgets(tmp, MAX_LINE, fp) != NULL) { if (rand() % i < k) { // 隨機替換已有的某一行 int j = rand() % k; strcpy(randomLine[j], tmp); } ++i; } }
問題描述:假設有一個數組,包含n個元素。如今要從新排列這些元素,要求每一個元素被放到任何一個位置的機率都相等(即1/n),
而且直接在數組上重排(in place),不要生成新的數組。用O(n) 時間、O(1)輔助空間。
解題思路:
先想一想若是能夠開闢另一塊長度爲n的輔助空間時該怎麼處理,顯然只要對n個元素作n次(不放回的)隨機抽取就能夠了。
先從n個元素中任選一個,放入新空間的第一個位置,而後再從剩下的n-1個元素中任選一個,放入第二個位置,依此類推。
按照一樣的方法,但此次不開闢新的存儲空間。第一次被選中的元素就要放入這個數組的第一個位置,但這個位置原來已經有別的(也可能就是這個)元素了,這時候只要把原來的元素跟被選中的元素互換一下就能夠了。很容易就避免了輔助空間。
void Random(std::vector<int>& num) { srand(time(NULL)); for (int i = num.size() - 1; i >= 0; --i) { int pos = rand() % (i + 1); std::swap(num.at(i), num.at(pos)); } }
問題描述:有一枚不均勻的硬幣,已知拋出此硬幣後,正面向上的機率爲p(0 < p < 1)。請利用這枚硬幣產生出機率相等的兩個事件
某一次拋出硬幣,正面向上的機率是p,反面向上的機率是1 - p,當p不等於0.5時,這兩個事件的機率就不同了。
怎麼能湊出等機率呢?仍是要利用機率的加法和乘法法則。這裏用乘法,也就是連續的獨立事件。
連續拋兩次硬幣,正反面的出現有四種狀況,機率依次爲:
兩次均爲正面:p * p 第一次正面,第二次反面:p * (1 - p) 第一次反面,第二次正面:(1 - p) * p 兩次均爲反面:(1 - p) * (1 - p)
中間兩種狀況的機率是徹底同樣的。因而問題的解法就是連續拋兩次硬幣,若是兩次獲得的相同則從新拋兩次;
不然根據第一次(或第二次)的正面反面狀況,就能夠獲得兩個機率相等的事件
擴展:
這一題實質就是 利用不均勻機率事件 生成 [0,1]之間的隨機數
如何利用 不均勻機率事件 生活[0,n)之間的隨機數呢?(即0,1,2... n-1這n個數)
咱們每一輪都是連續拋n次硬幣
每一輪中 正面出現次數爲i,則反面出現次數爲 n-i,相應機率爲 C(n, i) * (Pi) * (1-p)(n-i)
當i=1或者n-1時,上述機率分別爲 n*p*(1-p)(n-1) 和 n*p(n-1)*(1-p)
注意到上面2個機率的係數都是n,咱們如今須要隨機生成n個數,咱們就能夠將這兩點聯繫起來
例如i=1,對應n種狀況,咱們將這n種狀況對應編號 n個數
對於每一輪中正面出現次數不是1的時候,就從新再拋一輪