《编程珠玑》代码之路17:如何产生指定概率的随机不重复大数

我们都知道,C语言函数库的随机函数应用范围十分有限,RAND_MAX也就2^15那么大,如果单纯的扩展范围,那么精度自然就是问题了,不仅如此,在RAND_MAX不够大的情况下,产生小于指定数的精度也是非常受限的。所以本文:

1:会实现一个最大能到2^30,精确的随机函数。

2:会用不同思路实现不同时间复杂度的等概率不重复随机数列算法。

好了,进入正题:

如何实现一个更大精度的随机函数呢?首先我们知道计算机里的数,其实都是用二进制位表示的,也就是说,我们只需要生成随机二进制位就OK了。

用RAND_MAX * rand() + rand()的方式就可以啦,RAND_MAX * rand()用来构造高位,后面的+rand()用来构造低位,代码如下:

int bigRand(){
	return RAND_MAX * rand() + rand();
}

基于bigRand()函数,我们可以实现更精确的产生某个范围的随机函数randInt(),代码如下:

int randInt(int left, int right){
	return left + bigRand() % (right - left + 1);
}

left和right指定要生成数的区间范围,包括端点。

接下来,我们实现几个按顺序等概率生成没有重复数序列的代码,如何做到等概率呢?

比如说要以2/5也就是以40%的概率生成一个数:

其实很简答:bigRand() % 5 < 2,因为bigRand()生成数的范围非常大,所以完全可以认为,取模后的结果均匀分布在0-4,小于2就是0和1。这个结论《计算机程序设计艺术》的作者knuth给了概率上的证明。

这个代码,等概率的产生一个有序的随机数列,m为随机数数目,n为最大数:

int genKnuth(int m, int n){

	for (int i = 0; i < n; ++i){
		if ((bigRand() % (n - i)) < m){
			randNums.push_back(i);
			m -= 1;
		}
	}
	return 0;
}

可以看到,这个算法复杂度和n成正比,当n非常大的时候效率挺一般的。

所以,有一个更好的算法:每次生成一个随机数,如果这个随机数没有出现过,那么就加入到序列中,如果重复,就继续随机过程,判重的过程我们用c++的set数据结构完成,这样时间复杂度可以降为mlogm级别,通常m远小于n,所以性能更好:

int getSets(int m, int n){

	set<int> s;

	while (s.size() < m){
		s.insert(bigRand() % n);
	}

	set<int>::iterator it;

	for (it = s.begin(); it != s.end(); ++it){
		randNums.push_back(*it);
	}

	return 0;
}

还有人提出了一个很好玩的算法,博主一并实现放在了一起,getShuf()函数,有兴趣的读者可以看看。

全部代码如下:

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <vector>
#include <set>
#include <algorithm>
#include <ctime>

using namespace std;

vector<int> randNums;

int bigRand();
int randInt(int left, int right);
int genKnuth(int m, int n);
int getSets(int m, int n);
int getShuf(int m, int n);

int main(){
	srand(time(NULL));
	for (int i = 0; i < 5; ++i){
		cout << bigRand() << ' ';
	}cout << endl;

	for (int i = 0; i < 20; ++i){
		cout << randInt(100, 200) << ' ';
	}cout << endl;

	genKnuth(20, 200);
	for(int i = 0; i < randNums.size(); ++i){
		cout << randNums[i] << ' ';
	}cout << endl;

	randNums.clear();

	getSets(20, 200);
	for(int i = 0; i < randNums.size(); ++i){
		cout << randNums[i] << ' ';
	}cout << endl;
	randNums.clear();

	getShuf(20, 200);
	for(int i = 0; i < randNums.size(); ++i){
		cout << randNums[i] << ' ';
	}cout << endl;
	randNums.clear();

	return 0;
}



int bigRand(){
	return RAND_MAX * rand() + rand();
}

int randInt(int left, int right){
	return left + bigRand() % (right - left + 1);
}

int genKnuth(int m, int n){

	for (int i = 0; i < n; ++i){
		if ((bigRand() % (n - i)) < m){
			randNums.push_back(i);
			m -= 1;
		}
	}
	return 0;
}

int getSets(int m, int n){

	set<int> s;

	while (s.size() < m){
		s.insert(bigRand() % n);
	}

	set<int>::iterator it;

	for (it = s.begin(); it != s.end(); ++it){
		randNums.push_back(*it);
	}

	return 0;
}

int getShuf(int m, int n){
	int *x = new int[n];

	for (int i = 0; i < n; ++i){
		x[i] = i;
	}

	for (int i = 0; i < m; ++i){
		int j = randInt(i, n - 1);
		swap(x[i], x[j]);
	}

	sort(x, x + m);

	for (int i = 0; i < m; ++i){
		randNums.push_back(x[i]);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/beijixiong5622/article/details/84876341