一道面试题引发的有关随机数的思考(3)

在上一篇文章(http://blog.csdn.net/xzjxylophone/article/details/6835332)中,我们主要完成了这个C#工程的重构和Rand10,Rand12的另一种实现。

这次我们来实现如果用Rand7来产生一个Rand11.

按照以前的思路计算Rand10的时候是分成2个集合(1,3,5,7,9)(2,4,6,810)

那么我们是否可以把11也分成2个集合(1,3,5,7,9,11)和(2,4,6,8,10);

按照10的思路,在Rand10.Next()函数中应该把代码:

if (n > 4)
	num *= 2;
else 
	num = num * 2 - 1;

在Rand11.Next()修改成:

if (n > 4)
	num = 2*num-1;//1,3,5,7,9,11
else 
	num = 2 * Rand5;//此处的Rand5表示随即产生一个1-5的一个数

为了完成Rand11,那必须先实现Rand5.

Rand5

public class Rand5 : Rand7Base
{
	private Rand5()
	{
		_maxNum = 5;
	}
	public static Rand5 GetInstance()
	{
		if (rand == null || !(rand is Rand5))
		{
			rand = new Rand5();
		}
		return (Rand5)rand;
	}
	//获得随机数   
	override public int Next()
	{
		int num;
		//均匀产生1、 2 、3、4、5   
		while (true)
		{
			num = _rand7.Next();
			if (num <= 5)
				return num;
		}
	}  
}
按照前述的思路完成Rand11:

public class Rand11 : Rand7Base
{
	private Rand11()
	{
		_maxNum = 11;
	}
	public static Rand11 GetInstance()
	{
		if (rand == null || !(rand is Rand11))
		{
			rand = new Rand11();
		}
		return (Rand11)rand;
	}
	//获得随机数   
	override public int Next()
	{
		
		int num;
		
		label:
		while (true) //代码块1
		{
			num = _rand7.Next();
			if (num <= 6)
				break;
		}
		//1<=num<=6
		//1.3.5.7.9.11  num*2-1
		//2.4.6.8.10    num*2

		while (true)
		{
			int n = _rand7.Next();
			if (n == 1)
				continue;
			if (n > 4)
				num = num * 2 - 1; // 代码块2
			else
			{
				num = 2 * Rand5.GetInstance().Next();//随即获取1-5,保证num为(2,4,6,8,10)  //代码块3
			} 
			break;
		}
		return num;
	}   
}

添加测试函数:

static void TestRand5()
{
	Rand5 rand = Rand5.GetInstance();
	TestRand(rand);
}
static void TestRand11()
{
	Rand11 rand = Rand11.GetInstance();
	TestRand(rand);
}
测试Rand5和Rand11得到的测试结果:

计算Rand5的概率如下
产生1的概率是:0.1998812
产生2的概率是:0.2001405
产生3的概率是:0.1998788
产生4的概率是:0.2001452
产生5的概率是:0.1999543
耗时: 1.35574716655683 秒
Average:0.2000000;Max:0.2001452;Min:0.1998788
Max:0.2001452, Max-Average:0.0001452, Bits:0.0726000%
Min:0.1998788, Min-Average:-0.0001212, Bits:-0.0606000%
计算Rand11的概率如下
产生1的概率是:0.0833986
产生2的概率是:0.0998455
产生3的概率是:0.0832477
产生4的概率是:0.1000255
产生5的概率是:0.0832922
产生6的概率是:0.1000058
产生7的概率是:0.0833212
产生8的概率是:0.0999634
产生9的概率是:0.0834429
产生10的概率是:0.1001360
产生11的概率是:0.0833212
耗时: 2.95999916630916 秒
Average:0.0909091;Max:0.1001360;Min:0.0832477
Max:0.1001360, Max-Average:0.0092269, Bits:10.1496000%
Min:0.0832477, Min-Average:-0.0076614, Bits:-8.4275300%

Rand5是正确的。Rand11的误差范围居然在-8%----10%这里,说明Rand11是有问题。

重新观察Rand10和Rand11.在Rand10中虽然分配了2个集合,但是每个集合的元素都是相同的,但是在Rand11中2个集合,一个集合是6个元素,一个集合是5个元素,问题肯定就出现在这里。

注意到程序运行到代码块2和代码块3的概率都是一样的都为1/2

只不过概率1/2被6个元素给分了(1,3,5,7,9,11),而剩下的1/2被剩余的5个元素分了(2,4,6,8,10)

所以在输出的结果中可以看到P(1),P(3),P(5),P(7),P(9),P(11)的概率都约等于1/12而其他数的概率约等于1/10。


现在可以这样来考虑,为了让1-11这11个数都是等概率的得到,可以把概率1看成一块蛋糕分给11个人,这个该如何分了?

可以这样来分:先平均分成12份,每个人拿一份,最后剩下一份,然后把剩下的那一份再平均分12份,依次类推。

现在我们用数学知识来计算一个人(A)到底分配了多少蛋糕:

步骤1:第一次分12份,分给11人后,A得到了1/12,还剩下1/12

步骤2:把步骤1剩下的一份在分成12份,分给11人后,  A得到了1/12   *   1/12,剩余1/12 * 1/12

步骤3:把步骤2剩余的一份在分成12份,分给11人后, A得到 1/12 * pow(1/12,  2)                    //pow(a,b)表示a的b次方

依次类推

可的A得到的蛋糕:P(A)=1/12 + 1/12 * 1/ 12 + 1/12 * pow(1/12,2) + ..... + 1/ 12 * pow(1/12, n)= (1/12) * (1- pow(1/12, 2)) / (1 - 1/12)  //等比数列计算公式 = (1/12 ) / (11/12) = 1 / 11

结果为1/11是我们想要的结果,那么可以根据依然的想法来编写程序:

Rand11_2

public class Rand11_2 : Rand7Base
{
	private Rand11_2()
	{
		_maxNum = 11;
	}
	public static Rand11_2 GetInstance()
	{
		if (rand == null || !(rand is Rand11_2))
		{
			rand = new Rand11_2();
		}
		return (Rand11_2)rand;
	}
	//获得随机数   
	override public int Next()
	{
		int num;
		while (true)
		{
			num = _rand7.Next();
			if (num <= 6)
				break;
		}
		while (true)
		{
			int n = _rand7.Next();
			if (n == 1)
				continue;
			if (n > 4)
				num = num * 2 - 1;//1,3,5,7,9,11
			else
			{
				//当num = 6的时候表示需要继续的划分蛋糕
				if (num == 6)
				{
					num = Next();
				}
				else
				{
					num = 2 * num;
				}
				
			}
			break;
		}
		return num;
	}   
}

按照以前的方法添加测试代码并测试

此次测试Rand11和Rand11_2,这样可以进行比较:

计算Rand11的概率如下
产生1的概率是:0.0831913
产生2的概率是:0.1001187
产生3的概率是:0.0833342
产生4的概率是:0.0999278
产生5的概率是:0.0834714
产生6的概率是:0.1000888
产生7的概率是:0.0833103
产生8的概率是:0.1001032
产生9的概率是:0.0832276
产生10的概率是:0.1000561
产生11的概率是:0.0831706
耗时: 3.28849131413559 秒
Average:0.0909091;Max:0.1001187;Min:0.0831706
Max:0.1001187, Max-Average:0.0092096, Bits:10.1305700%
Min:0.0831706, Min-Average:-0.0077385, Bits:-8.5123400%
计算Rand11_2的概率如下
产生1的概率是:0.0908736
产生2的概率是:0.0908268
产生3的概率是:0.0908969
产生4的概率是:0.0909283
产生5的概率是:0.0909252
产生6的概率是:0.0908479
产生7的概率是:0.0908451
产生8的概率是:0.0909375
产生9的概率是:0.0909681
产生10的概率是:0.0910002
产生11的概率是:0.0909504
耗时: 2.48036752188162 秒
Average:0.0909091;Max:0.0910002;Min:0.0908268
Max:0.0910002, Max-Average:0.0000911, Bits:0.1002200%
Min:0.0908268, Min-Average:-0.0000823, Bits:-0.0905200%

这样就出现我们所希望的结果了,也就是说我们的假设是正确的了。

既然Rand11_2可以用这样的方法去解决了,那么Rand10或者Rand12能不能用类似的思路去解决这个问题了?

下一篇我们来尝试用这种思路实现Rand10.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章