随机数问题目录

  • 随机数问题简述
  • 第一种方法 线性同余法求随机数
  • 第二种方法 采用延迟的方法避免重复
  • 第三种方法 采用数组保存索引判断是否有重复
  • 第四种方法 采用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;
    }

总结

为了 精确 推荐采用第一种方法,为了 快速无重复 推荐使用第三种方法