问题:如何得到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
关于思路3第一个版本的分析,参考自:http://bbs.bccn.net/thread-331122-1-1.html