随机数问题目录
- 随机数问题简述
- 第一种方法 线性同余法求随机数
- 第二种方法 采用延迟的方法避免重复
- 第三种方法 采用数组保存索引判断是否有重复
- 第四种方法 采用Hashtable判断是否有重复值
- 第五种方法 采用递归判断是否有重复值
- 总结
随机数问题简述
此篇文章主要解决在同一时间内,多次调用
System.Random.Next()
UnityEngine.Random.Range()
等使用C#语言的编译环境,产生重复值的问题(除去偶然性)
为了方便,下面代码全部可以直接复制粘贴使用
第一种方法 线性同余法求随机数
线性同余随机数生成器公式
X(n+1) = (a * X(n) + c) % m
古老的LCG(linear congruential generator)代表了最好最朴素的伪随机数产生器算法。主要原因是容易理解,容易实现,而且速度快。
其中,各系数为:
模m, m > 0
系数a, 0 < a < m
增量c, 0 <= c < m
原始值(种子) 0 <= X(0) < m
其中参数c, m, a比较敏感,或者说直接影响了伪随机数产生的质量。
一般而言,高LCG的m是2的指数次幂(一般232或者264),因为这样取模操作截断最右的32或64位就可以了。多数编译器的库中使用了该理论实现其伪随机数发生器rand()。
第N+1个数 = ( 第N个数 * A + B) % M
上面的公式中A、B和M分别为常数,是生成随机数的因子,如果之前从未通过同一个Random对象生成过随机数(也就是调用过Next方法),那么第N个随机数为将被指定为一个默认的常数,这个常数在创建一个Random类时被默认值指定,Random也提供一个构造函数允许开发者使用自己的随机数因子,这一切可通过微软官方开源代码看到:
public Random() : this(Environment.TickCount) { } public Random(int Seed) { }
为了生成更加可靠的随机数,微软在System.Security.Cryptography命名空间下提供一个名为RNGCryptoServiceProvider的类,它采用系统当前的硬件信息、进程信息、线程信息、系统启动时间和当前精确时间作为填充因子,通过更好的算法生成高质量的随机数。下面直接贴代码了:
/// <summary>
/// 随机数方法
/// </summary>
/// <param name="count">取随机数的个数</param>
/// <param name="maxnum">随机数的最小值(包含)</param>
/// <param name="minnum">随机数的最大值(包含)</param>
/// <returns>结果数组</returns>
public int[] RandomFunction(int count,int minnum, int maxnum)
{
int[] result=new int[count];
System.Random random = new System.Random(GetRandomSeed());
for (int i = 0; i < count; i++)
{
result[i] = random.Next(minnum, maxnum + 1);
}
return result;
}
/// <summary>
/// 拿到随机种子
/// </summary>
/// <returns></returns>
private int GetRandomSeed()
{
byte[] bytes = new byte[4];
System.Security.Cryptography.RNGCryptoServiceProvider rng =
new System.Security.Cryptography.RNGCryptoServiceProvider();
rng.GetBytes(bytes);
return BitConverter.ToInt32(bytes, 0);
}
还有一种方法与这种类似,可以直接归类到这里
使用 System.Guid().GetHashCode() 来替换 RandomSeed
第二种方法 采用延迟的方法避免重复
这种其实没什么用,但还是列出来吧,随机数因子默认为时钟,所以等一下就好了,一般也不用这种方法
System.Random random = new System.Random();
Debug.Log(random.Next(1, 100).ToString());
Thread.Sleep(100);
Debug.Log(random.Next(1, 100).ToString());
Thread.Sleep(100);
Debug.Log(random.Next(1, 100).ToString());
第三种方法 采用数组保存索引判断是否有重复
思想是用一个数组来保存索引号,先随机生成一个数组位置,然后把随机抽取到的位置的索引号取出来,并把最后一个索引号复制到当前的数组位置,然后使随机数的上限减一。这样就保证了取的值一定不会是重复的,但也有个缺点
取随机数的个数一定不能大于最大值最小值的区间
/// <summary>
/// 随机数方法
/// </summary>
/// <param name="count">取随机数的个数</param>
/// <param name="minnum">随机数的最小值(包含)</param>
/// <param name="maxnum">随机数的最大值(包含)</param>
/// <returns>结果数组</returns>
public int[] RandomFunction(int count,int minnum, int maxnum)
{
//数据总量 最大值
int numCount = maxnum;
//结果数量
int resCount = count;
//设置下限
int site = numCount;
//新建数据总量位的数组
int[] index = new int[numCount];
//分别赋值
for (int i = 0; i < numCount; i++)
index[i] = i;
System.Random r = new System.Random();
//用来保存随机生成的不重复的结果数量的个数
int[] result = new int[resCount];
int id;
for (int j = 0; j < resCount; j++)
{
id = r.Next(minnum, site - 1);
//在随机位置取出一个数,保存到结果数组
result[j] = index[id];
//最后一个数复制到当前位置
index[id] = index[site - 1];
//位置的下限减少一
site--;
}
return result;
}
第四种方法 采用Hashtable判断是否有重复值
这个不用多解释,利用Hashtable对比表的值保证每个随机数只有一个
不建议大量数据使用
/// <summary>
/// 随机数方法
/// </summary>
/// <param name="count">取随机数的个数(不建议取过大)</param>
/// <param name="minnum">最小值(包含)</param>
/// <param name="maxnum">最大值(包含)</param>
/// <returns>结果数组</returns>
public int[] RandomFunction(int count,int minnum,int maxnum)
{
Hashtable hashtable = new Hashtable();
System.Random rm = new System.Random();
int RmNum = count;
int[] result = new int[count];
for (int i = 0; hashtable.Count < RmNum; i++)
{
int nValue = rm.Next(minnum,maxnum+1);
if (!hashtable.ContainsValue(nValue))
{
hashtable.Add(nValue, nValue);
result[i] = nValue;
}
}
hashtable = null;
return result;
}
第五种方法 采用递归判断是否有重复值
用它来检测生成的随机数是否有重复,如果取出来的数字和已取得的数字有重复就重新随机获取。
不建议大量数据使用
/// <summary>
/// 随机数方法
/// </summary>
/// <param name="count">取随机数的个数(不要过大)</param>
/// <param name="minnum">最小值(包含)</param>
/// <param name="maxnum">最大值(包含)</param>
/// <returns>结果数组</returns>
public int[] RandomFunction(int count, int minnum, int maxnum)
{
System.Random ra = new System.Random(unchecked((int)System.DateTime.Now.Ticks));
//生成多少个
int Count = count;
int[] arrNum = new int[Count];
int tmp = 0;
//最小值
int minValue = minnum;
//最大值
int maxValue = maxnum;
for (int i = 0; i < Count; i++)
{
tmp = ra.Next(minValue, maxValue+1); //随机取数
arrNum[i] = getNum(arrNum, tmp, minValue, maxValue, ra); //取出值赋到数组中
}
return arrNum;
}
public int getNum(int[] arrNum, int tmp, int minValue, int maxValue, System.Random ra)
{
int n = 0;
while (n <= arrNum.Length - 1)
{
if (arrNum[n] == tmp) //利用循环判断是否有重复
{
tmp = ra.Next(minValue, maxValue); //重新随机获取。
getNum(arrNum, tmp, minValue, maxValue, ra);//递归:如果取出来的数字和已取得的数字有重复就重新随机获取。
}
n++;
}
return tmp;
}
总结
为了 精确 推荐采用第一种方法,为了 快速无重复 推荐使用第三种方法