如何产生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



关于思路3第一个版本的分析,参考自:http://bbs.bccn.net/thread-331122-1-1.html

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值