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

既然在前2篇文章中我们已经实现了RandN小于等于49的随机数,上一篇文章(http://blog.csdn.net/xzjxylophone/article/details/6853727)留下的问题:可否用任意Rand7实现任意一个数的随机数

这篇文章就来讨论该如何实现。

注意到我们计算RandN的时候用到34=7*4 + 6,那么计算其他的数,如101,可以想到101=2 * 49 + 3 = 2*pow(7,2) + 0 * pow(7, 1) + 3 * pow(7,0)

这个 让我们想到进制(二进制,十六进制等等)了,基于这个思路我们实现如下的代码:

首先NumAnalysze类:该类的作用是把一个数表示成一个基于我们给定的进制,用一个链表保存其结果

public class NumAnalyse
{
	private int _baseNum;//7:类似七进制
	private List<int> _list;//203
	//注意边界的问题
	/// <summary>
	/// 构造函数
	/// </summary>
	/// <param name="baseNum">是几进制的数据</param>
	/// <param name="reaNum">实际的十进制数据</param>
	public NumAnalyse(int baseNum, int reaNum)
	{
		//在本例中, _rand7.Next() - 1;
		//考虑边界的问题,在这里需要好好的琢磨一下代码
		reaNum--;//注意边界的问题
		_baseNum = baseNum;
		ResetList();
		//得到的是倒序
		while (reaNum >= _baseNum)
		{
			int remain = reaNum % baseNum;
			reaNum = reaNum / baseNum;
			_list.Add(remain);
		}
		if (reaNum != 0)
		{
			_list.Add(reaNum);
		}
		//最后反转过来
		_list.Reverse();
	}
	public NumAnalyse(int baseNum)
	{
		_baseNum = baseNum;
		ResetList();
		_list.Add(0);
	}
	//重新设置
	private void ResetList()
	{
		if(_list != null)
		{
			_list.Clear();
		}
		else
		{
			_list = new List<int>();
		}
	}
	public int GetReaNum
	{
		get
		{
			int result = 0;
			int i = 0;
			for(i = 0; i < _list.Count; i++)
			{
				result += (_list[i]) * (int)Math.Pow(_baseNum, _list.Count - i - 1);
			}
			//+1  跟构造函数的类似,需要注意边界的问题
			return result + 1;
		}
	}
	public List<int> NumList
	{
		get
		{
			return _list;
		}
	}
}

在看RandSN类的实现:

public class RandSN : Rand7Base
{
	private NumAnalyse na;//maxNum对应的伪进制数
	private RandSN(int maxNum)
	{
		UpdateMaxNum(maxNum);
	}
	public void UpdateMaxNum(int maxNum)
	{
		_maxNum = maxNum;
		na = new NumAnalyse(7, maxNum);
	}
	public static RandSN GetInstance(int maxNum)
	{
		if (rand == null || !(rand is RandSN))
		{
			rand = new RandSN(maxNum);
		}
		else if (rand is RandSN && rand.MaxNum != maxNum)
		{
			((RandSN)rand).UpdateMaxNum(maxNum);
		}
		return (RandSN)rand;
	}
	override public int Next()
	{
		int result = 0;
		NumAnalyse tempNa = new NumAnalyse(7);
		//注意边界问题
		int num = _rand7.Next() - 1;
		//表示前几位是否是一样的
		bool preEqual = true;
		for (int i = 0; i < na.NumList.Count; i++)
		{
			//必须有&& preEqual
			//可以想象十进制如何比较2个数的大小的
			if (num > na.NumList[i] && preEqual)
			{
				result = Next();
				break;
			}
			else
			{
				tempNa.NumList.Add(num);
				//如果前面是相等的,就继续判断,否则就不判断了
				if(preEqual)
				{
					preEqual = (num == na.NumList[i]);
				}
				num = _rand7.Next() - 1;
			}

		}
		//此处的result表示重新计算了
		return result != 0 ? result : tempNa.GetReaNum;
	}
}

添加测试函数:

static void TestRandSN()
{
	RandSN rand = RandSN.GetInstance(101);
	TestRand(rand);
	rand = RandSN.GetInstance(572);
	TestRand(rand);
}



测试结果:

计算Rand101的概率如下
耗时: 24.0733554924083 秒
Average:0.0099010;Max:0.0099738;Min:0.0098305
Max:0.0099738, Max-Average:0.0000728, Bits:0.7353800%
Min:0.0098305, Min-Average:-0.0000705, Bits:-0.7119500%
计算Rand572的概率如下
耗时: 30.9447089193863 秒
Average:0.0017483;Max:0.0017927;Min:0.0017121
Max:0.0017927, Max-Average:0.0000444, Bits:2.5424400%
Min:0.0017121, Min-Average:-0.0000362, Bits:-2.0678800%

关于Rand572的偏差是在2.54%左右,我想主要原因是,对于Rand572来说测试的总次数太小了,有兴趣的朋友可以扩大N倍试一试

或许在测试函数,应该计算方差而不是简单差概率。在此这一点先不管了。


到现在为止,我们一直是以Rand7为例子的,在这种算法中,是用到类似于进制的去解决的。我想应该能写一个更基本的算法,抛弃Rand7,可以用任意一个rand函数,如Rand10,rand20,rand16,rand2等等。

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