通常我们使用的随机函数是返回一个32位的取值范围为[0,0x7FFFFFFF) /*2147483647*/的随机数。
因为目前我们所使用的随机数生成都是伪随机数(伪随机数是以相同的概率从一组有限的数字中选取的,所选数字并不具有完全的随机性。),所以会造成分布不均的情况。
但是,如果生成的随机数能均匀分布的话(0~0x7FFFFFFF出现的概率一样),问题就解决了吗?
先看下面代码:
class Random { int index = 0; int []array; public Random(int len) { array = new int[len]; InitArray(); } void InitArray() { for(int i=0;i<array.Length;++i) { array[i] = i; } } public int Next() { int result = array[index++]; if(index >= array.Length) { index = 0; } return result; } } class Program { public static void Main(string[] args) { int alen = 12; int []arr = new int[alen]; int rlen = 16; Random r = new Random(rlen); int cnt = rlen * 10000; // int cnt = alen * 10000; for(int i=0;i<cnt;++i) { arr[r.Next() % alen] ++; } PrintArray(arr); Console.ReadKey(); } public static void PrintArray<T>(T [] arr) { for(int i=0;i< arr.Length;++i) { Console.WriteLine(i+":"+arr[i]); } } }
代码中的Random类是自定义的“随机类”,初始化传入的参数是指该类能保证[0,value) 的均匀分布(好绕口)。例如初始化传入16,则在调用Random::Next()时保证随机出现0~15的概率是均等的。
在Main函数中,Random r = new Random(16),但是真实测试数据只需要12个。以下是输出结果:
0:20000 1:20000 2:20000 3:20000 4:10000 5:10000 6:10000 7:10000 8:10000 9:10000 10:10000 11:10000
“什么情况?为何0~3的次数出现了这么多?!肯定是程序有问题!”
仔细发现Random函数的Next其实就是每次按顺序地返回,根本没有随机效果!
好吧,我们改代码试试:
class Util { public static void Swap<T>(ref T a, ref T b) { T t = a; a = b; b = t; } public static void Shuffle<T>(T []arr, System.Random r) { for(int i=arr.Length-1;i>0;--i) { Swap(ref arr[i], ref arr[r.Next()%arr.Length]); } } } class Random { System.Random r; int index = 0; int []array; public Random(int len) { r = new System.Random(); array = new int[len]; InitArray(); } void InitArray() { for(int i=0;i<array.Length;++i) { array[i] = i; } Util.Shuffle(array,r); } public int Next() { int result = array[index++]; if(index >= array.Length) { index = 0; Util.Shuffle(array,r); } //Console.Write(result+" "); return result; } } class Program { public static void Main(string[] args) { int alen = 12; int []arr = new int[alen]; int rlen = 16; Random r = new Random(rlen); int cnt = alen * 10000; //这里改成12,看上去"更公平点" // int cnt = rlen * 10000; //System.Random rr = new System.Random(); //Console.WriteLine(rr.Next()); for(int i=0;i<cnt;++i) { arr[r.Next() % alen] ++; } PrintArray(arr); Console.ReadKey(); } public static void PrintArray<T>(T [] arr) { for(int i=0;i< arr.Length;++i) { Console.WriteLine(i+":"+arr[i]); } } }
在来看看测试结果:
0:15000 1:15000 2:15000 3:15000 4:7500 5:7500 6:7500 7:7500 8:7500 9:7500 10:7500 11:7500
可能你还会找茬说Shuffle函数中所调用的随机数也是不能均匀分布,但是我保证了自己的Random类所产生的数是均匀分布就行了。
其实还有一个更简单的例子,就是16 与12调换,也就是说 你随机生成只能是[0,12),而[12,16)的范围永远取不到!(请自行脑部 int Random::Next() 和想要获取long.MaxValue的取值范围)。
绕了这么久,其实我想说明的是:就算 int Random::Next()能保证生成的随机数是均匀分布,但都不代表你所需要的取值范围能均匀分布(无论取值范围比它大还是小)。
如果想要获的自己所需的取值范围的而且又能体现随机分布的办法有两种:
一是我上面代码的方法(Random(12)就可以了),这种也不能完美解决,因为实质上打乱数组时也是伪随机数,还有就是如果取值范围太大,根本就是浪费内存的做法。
二是真正地实现一个无穷大的取值范围的随机数。
参考资料:
Random 类: http://msdn.microsoft.com/zh-cn/library/vstudio/system.random.aspx