如何產生n個不重複的隨機數
問題:如何得到n個互相不重複的隨機數?
用過Matlab的朋友,應該會見到過這樣一個函式:randperm(n),用途是產生n個不重複的隨機數。C和C++中也有用於產生隨機數的rand()函式。那麼,如何藉助這個函式去實現randperm(n)的效果呢?本文提供三種常見思路,如下:
思路1:
建立一個動態陣列,每次產生一個隨機數,並判斷該動態陣列是否含有這一隨機數。如果有,則跳過並重新產生下一個隨機數;如果沒有,則將產生的這一隨機數加入動態陣列。迴圈直到動態陣列中元素數量達到n。
vector<int> random1(int number) { vector<int> result; result.clear(); result.reserve(number); srand((int)time(0)); while (result.size() < number) { int value = rand() % number; bool flag = true; for (size_t i = 0; i < result.size(); i++) { if (result.at(i) != number) continue; else { flag = false; break; } } if (flag) { result.push_back(value); } } return result; }
思路2:
先產生一個數組,長度為n,每一個元素的值等於其索引號。然後從這個陣列中任意位置取出一個數字,並新增到另外一個動態陣列結尾處。迴圈直到第一個陣列元素數量為0。
vector<int> random2(int number) { vector<int> list; vector<int> result; list.clear(); result.clear(); list.reserve(number); result.reserve(number); srand((int)time(0)); for (int i = 0; i < number; i++) { list.push_back(i); } int length = list.size(); for (int i = 0; i < length; i++) { int index = rand() % list.size(); result.push_back(list[index]); list.erase(list.begin() + index); } return result; }
思路3:
根據題意,要得到的是n個互相不重複的隨機數,那麼對於思路2中產生的數值等於索引號的陣列,其內部元素隨意調換位置,完全可以達到題目的要求。這就是所謂的洗牌演算法。
vector<int> random3(int number) { vector<int> result; result.clear(); result.reserve(number); srand((int)time(0)); for (size_t i = 0; i < number; i++) { result.push_back(i); } int p1; int p2; int temp; int count = number; int i = 0; while (number--) { p1 = i++; p2 = rand() % count; temp = result[p1]; result[p1] = result[p2]; result[p2] = temp; } return result; }
那麼,這三種思路的實際執行效率如何呢?
我們使用C++的庫函式GetTickCount()來進行實驗,用這三種方法得到10000個互相不重複的隨機數,對比程式執行的時間。
思路1:4189ms
思路2:43ms
思路3用了甚至不到1ms的時間。
使用思路3產生100000個不重複隨機數:63ms
思路2得到100000個所用時間為:1983ms
那麼,是不是思路3就是最完美的答案了?可惜不是。
這個洗牌演算法是有問題的。
思路2所產生的隨機數列,總共有n!種排列方式,即n個數字的全排列為A(n, n),這種情況是可以保證所有情況機會均等的。
但是思路3總共迴圈了n次,每次都會導致n種可能,所以共有pow(n, n)種可能。
就n!而言,根據伯特蘭—切比雪夫定理,對於所有大於1的整數n,存在一個質數p,符合n < p < 2n。所以,n與n/2之間也一定存在一個素數,而且不能被n整除,pow(n, n)也無法整除。因此,pow(n, n)一定不是n!的整數倍。
這就意味著,思路3所產生的所有排列方式機會一定不是均等的。
那麼,該如何改進思路3,從而使得機會均等呢?其實很簡單。總共迴圈n次,只要讓每次導致的可能數量-1,那麼最終結果就是n!中情況了。程式碼如下:
vector<int> random3(int number)
{
vector<int> result;
result.clear();
result.reserve(number);
srand((int)time(0));
for (size_t i = 0; i < number; i++)
{
result.push_back(i);
}
int p1;
int p2;
int temp;
int count = number;
while (--number)
{
p1 = number;
p2 = rand() % number;
temp = result[p1];
result[p1] = result[p2];
result[p2] = temp;
}
return result;
}
思路3(改進版)得到100000個不同隨機數的執行時間:62ms