1. 程式人生 > >如何產生n個不重複的隨機數

如何產生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:0ms

思路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