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

在上一篇文章(http://blog.csdn.net/xzjxylophone/article/details/6853589)的最后我们提出如何用Rand7实现比较大的数字,例如Rand35,Rand34等等

这篇文章中我们来尝试新建一个类:RandN表示是任意小于49的一个随机数

首先看Rand10_3,我们可以这样思考:10=7*1 + 3,因此我们再第二步使用了如下的语句:

//num=4,5,6,7的时候重新计算
if(num > 3)
{
	result = Next();
}

把num=4,5,6,7的数字给过滤掉,重新去计算Rand10

因此我们可以这样去计算RandN(以34为例子):

34=4*7 + 6,

先得到一个Rand7的数字:int remain = rand7.Next()

然后在得到数字:int ceiling = _rand7.Next();//ceiling

如果ceiling 大于5,即ceiling =6,7

重新计算ceiling = _rand7.Next();//ceiling

否则

result=remain + 7 * ceiling;

在这里还要判断条件,就是remain这个参数:

如果remain大于6的话,那么就重新计算。

以下是RandN的实现:

//支持2-49,包括2和49
public class RandN : Rand7Base
{
	protected int _remain;       //剩余数,范围是0-6
	protected int _ceiling;      //大于的最小数
	private RandN(int maxNum)
	{
		UpdateMaxNum(maxNum);
	}
	public void UpdateMaxNum(int maxNum)
	{
		_maxNum = maxNum;
		_remain = _maxNum % 7;
		decimal chushu = (decimal)_maxNum / 7;
		_ceiling = (int)Math.Ceiling(chushu);
	}
	public static RandN GetInstance(int maxNum)
	{
		if (rand == null || !(rand is RandN))
		{
			rand = new RandN(maxNum);
		}
		//最大随机数不一样
		else if(rand is RandN && rand.MaxNum != maxNum)
		{
			((RandN)rand).UpdateMaxNum(maxNum);
		}
		return (RandN)rand;
	}
	
	override public int Next()
	{
		int result = 0;
		int remain = _rand7.Next();
		bool loop = false;
		int ceiling = 0;
		while (!loop)
		{
			ceiling = _rand7.Next();//ceiling
			//重新计算
			if (ceiling > _ceiling)
				continue;

			//n-1保证result是有可能小于7
			result = remain + (ceiling - 1) * 7;

			//以下的代码可以被替代:
			if (ceiling == _ceiling && _remain != 0)//考虑整除的情况
			{
				if (remain > _remain)//剩余数超过最大的剩余数就重新计算
				{
					result = Next();
				}
			}

			//可以被替代成:
			//if(result > _maxNum)
			//{
			//    result = Next();
			//}

			loop = true;
		}
		return result;
	}
}




添加测试代码:

static void TestRandN()
{
	RandN rand = RandN.GetInstance(34);
	TestRand(rand);
	rand = RandN.GetInstance(35);
	TestRand(rand);
	rand = RandN.GetInstance(6);
	TestRand(rand);
}
测试结果:

计算Rand34的概率如下
产生1的概率是:0.0294017
产生2的概率是:0.0294027
产生3的概率是:0.0293998
产生4的概率是:0.0295281
产生5的概率是:0.0294838
产生6的概率是:0.0294388
产生7的概率是:0.0293086
产生8的概率是:0.0294455
产生9的概率是:0.0293082
产生10的概率是:0.0294202
产生11的概率是:0.0294208
产生12的概率是:0.0293530
产生13的概率是:0.0294430
产生14的概率是:0.0293526
产生15的概率是:0.0294120
产生16的概率是:0.0293657
产生17的概率是:0.0294057
产生18的概率是:0.0293856
产生19的概率是:0.0293732
产生20的概率是:0.0294238
产生21的概率是:0.0293502
产生22的概率是:0.0294515
产生23的概率是:0.0294136
产生24的概率是:0.0294762
产生25的概率是:0.0294245
产生26的概率是:0.0294585
产生27的概率是:0.0294599
产生28的概率是:0.0293607
产生29的概率是:0.0294267
产生30的概率是:0.0294665
产生31的概率是:0.0293348
产生32的概率是:0.0294346
产生33的概率是:0.0293828
产生34的概率是:0.0294867
耗时: 2.78373975842868 秒
Average:0.0294118;Max:0.0295281;Min:0.0293082
Max:0.0295281, Max-Average:0.0001163, Bits:0.3955400%
Min:0.0293082, Min-Average:-0.0001036, Bits:-0.3521200%
计算Rand35的概率如下
产生1的概率是:0.0286397
产生2的概率是:0.0285668
产生3的概率是:0.0286318
产生4的概率是:0.0285731
产生5的概率是:0.0285246
产生6的概率是:0.0285689
产生7的概率是:0.0284451
产生8的概率是:0.0287106
产生9的概率是:0.0285398
产生10的概率是:0.0284319
产生11的概率是:0.0285941
产生12的概率是:0.0285509
产生13的概率是:0.0286407
产生14的概率是:0.0286336
产生15的概率是:0.0285854
产生16的概率是:0.0286022
产生17的概率是:0.0286192
产生18的概率是:0.0285978
产生19的概率是:0.0286203
产生20的概率是:0.0286015
产生21的概率是:0.0285558
产生22的概率是:0.0285200
产生23的概率是:0.0285130
产生24的概率是:0.0285547
产生25的概率是:0.0285327
产生26的概率是:0.0285055
产生27的概率是:0.0285340
产生28的概率是:0.0285735
产生29的概率是:0.0285664
产生30的概率是:0.0285509
产生31的概率是:0.0286140
产生32的概率是:0.0285983
产生33的概率是:0.0284317
产生34的概率是:0.0286959
产生35的概率是:0.0285756
耗时: 3.08980588360172 秒
Average:0.0285714;Max:0.0287106;Min:0.0284317
Max:0.0287106, Max-Average:0.0001392, Bits:0.4871000%
Min:0.0284317, Min-Average:-0.0001397, Bits:-0.4890500%
计算Rand6的概率如下
产生1的概率是:0.1666733
产生2的概率是:0.1664507
产生3的概率是:0.1666722
产生4的概率是:0.1667893
产生5的概率是:0.1667764
产生6的概率是:0.1666381
耗时: 9.59583967306935 秒
Average:0.1666667;Max:0.1667893;Min:0.1664507
Max:0.1667893, Max-Average:0.0001226, Bits:0.0735800%
Min:0.1664507, Min-Average:-0.0002160, Bits:-0.1295800%

观察上述结果,误差都是在我们所在的范围内,但是有一点不知道有没有注意到,就是在计算Rand6的时候居然用了9.5秒。

观察代码可以发现当maxNum为6的时候,第二次ceiling=rand7.Next() 此时必须是1,否则是一直重新计算。所以是否可以用一个办法来优化是何时来重新计算ceiling。

在C#中可以用委托,或者C++中的函数指针。下一篇文章我们来优化这个算法。


你可能感兴趣的:(一道面试题引发的有关随机数的思考(5))