用 C# 生成不重复的随机数的三种方法
第一种方法:利用HashTable
/// <summary>
/// 利用Hashtable
/// </summary>
static int[] UseHashTableToNonRepeatedRandom(int length, int minValue, int maxValue)
{
Hashtable hashtable = new Hashtable();
int seed = Guid.NewGuid().GetHashCode();
Random random = new Random(seed);
for (int i = 0; hashtable.Count < length; i++)
{
int nValue = random.Next(minValue, maxValue);
if (!hashtable.ContainsValue(nValue) && nValue != 0)
{
hashtable.Add(i, nValue);
//hashtable.Add(nValue, nValue); // 将 key 和 value设置成一样的值,导致hashtable无法按添加顺序输出数组
//Console.WriteLine(nValue.ToString());
}
}
int[] array = new int[hashtable.Count];
hashtable.Values.CopyTo(array, 0);
return array;
}
注意事项:
1.该方法 length 和 maxValue的相关性比较强,使用时应该予以着重考虑
2.方法不适合生成 maxValue == length 相等的数据,
例如:不适合生成有10道考题的不重复题目号(要求从1-10,随机生成且不重复)
maxValue最好不等于length参数,
当 maxValue - minValue = length 时,时间效率差一个数量级【主要由于for循环内的生成语句导致】
实际测试生成1w个数 maxValue - minValue 是 length 的
2倍 :生成时间(使用7倍时)翻倍
7倍及以上:生成时间基本一致
3.使用HashTable保存 key 和 value 时,不要将同一个数值存入key和value,否则将导致数据顺序输出给数组
注意:
生成时间和具体使用的PC几性能有关,仅做参考!
效率说明:1w条数据测试
HashTable方法当ArrayNum_=_maxValue时
HashTable方法当maxValue是ArrayNum的1000倍时
第二种方法:递归(使用递归检测重复数)
/// <summary>
/// 检查生成的随机数据是否重复
/// 如果重复则继续递归调用
/// 如果不重复则返回该数
/// </summary>
/// <param name="arr">生成的非重复随机数数组</param>
/// <param name="temp">随机数</param>
/// <param name="minValue">最大值</param>
/// <param name="maxValue">最小值</param>
/// <param name="random">生成随机数对象</param>
/// <returns>返回一个不重复的随机数</returns>
static int getNumberNonRepeatedRandom(int[] arr, int temp, int minValue, int maxValue, Random random)
{
int n = 0;
while (n <= arr.Length - 1)
{
if (arr[n] == temp) //利用循环判断是否有重复
{
temp = random.Next(minValue, maxValue); //重新随机获取。
getNumberNonRepeatedRandom(arr, temp, minValue, maxValue, random);//递归:如果取出来的数字和已取得的数字有重复就重新随机获取。
}
n++;
}
return temp;
}
/// <summary>
/// 递归,用它来检测生成的随机数是否有重复,如果取出来的数字和已取得的数字有重复就重新随机获取。
/// </summary>
static int[] RecursiveMethodToNonRepeatedRandom(int length, int minValue, int maxValue)
{
int seed = Guid.NewGuid().GetHashCode();
Random random = new Random(seed);
int[] array = new int[length];
int temp = 0;
for (int i = 0; i < length; i++)
{
temp = random.Next(minValue, maxValue); // 随机取数
array[i] = getNumberNonRepeatedRandom(array, temp, minValue, maxValue, random); // 取出值赋到数组中
}
return array;
}
注意事项:
使用条件:
1.maxValue - minValue = length。
若差值过于接近或等于length将导致 Stack Overflow Excepion 异常
该异常一般是因为函数调用栈溢出,
也就是函数调用的层次超出了程序能够接受的范围.
这种情况通常是由函数循环调用导致无限递归而引起的
2.实际测试生成1w个数 maxValue 是 length 的
2倍及以上:生成时间基本一致
效率说明:1w条数据测试
maxValue - minValue = length 若差值过于接近或等于length将导致 Stack Overflow Excepion 异常
实际测试生成1w个数 maxValue 是 length 的 2倍及以上:生成时间基本一致
第三种方法:使用双数组策略
【思路】:是用一个数组来保存索引号,先随机生成一个数组位置,然后把随机抽取到的位置的索引号取出来,并把最后一个索引号复制到当前的数组位置,然后使随机数的上限减一。
具体如:先把这100个数放在一个数组内,每次随机取一个位置(第一次是1-100,第二次是1-99,…),将该位置的数用最后的数代替。
/// <summary>
/// 方法一 使用随机抽取数组index中的数,填充在新的数组array中,使数组array中的数是随机的
/// 方法一思路:用一个数组来保存索引号,先随机生成一个数组位置,然后把随机抽取到的位置的索引号取出来,
/// 并把最后一个索引号复制到当前的数组位置,然后使随机数的上限减一,具体如:先把这100个数放在一个数组内,
/// 每次随机取一个位置(第一次是1-100,第二次是1-99,...),将该位置的数用最后的数代替。
/// </summary>
static int[] UseDoubleArrayToNonRepeatedRandom(int length, int minValue, int maxValue)
{
int seed = Guid.NewGuid().GetHashCode();
Random radom = new Random(seed);
int[] index = new int[length];
for (int i = 0; i < length; i++)
{
index[i] = i + 1;
}
int[] array = new int[length]; // 用来保存随机生成的不重复的数
int site = length; // 设置上限
int idx; // 获取index数组中索引为idx位置的数据,赋给结果数组array的j索引位置
for (int j = 0; j < length; j++)
{
idx = radom.Next(0, site - 1); // 生成随机索引数
array[j] = index[idx]; // 在随机索引位置取出一个数,保存到结果数组
index[idx] = index[site - 1]; // 作废当前索引位置数据,并用数组的最后一个数据代替之
site--; // 索引位置的上限减一(弃置最后一个数据)
}
return array;
}
注意事项:
1.生成上w条不重复随机数据时,建议使用该方法;
2.该方法生成的不重复随机数,参数length 和 maxValue 没有相互的限制关系,适合生成类似随机考题的类似情况;
3.效率是三个方法中最高的,推荐使用;
三个方法的效率比较:
maxValue是length的1000倍进行测试
完整代码:
using System;
using System.Collections;
using System.Diagnostics;
namespace CSharp生成不重复的随机数
{
class Program
{
/// <summary>
/// 利用Hashtable
/// </summary>
/// <remarks>
/// 使用条件:
///
/// 1.该方法 length 和 maxValue的相关性比较强,使用时应该予以着重考虑
///
/// 2.方法不适合生成 maxValue == length 相等的数据,
/// 例如:生成有10道考题的不重复题目号(要求从1-10,随机生成且不重复)
/// maxValue最好不等于length参数,
/// 当 maxValue - minValue = length 时,时间效率差一个数量级【主要由于for循环内的生成语句导致】
/// 实际测试生成1w个数 maxValue - minValue 是 length 的
/// 2倍 :生成时间(使用7倍时)翻倍
/// 7倍及以上:生成时间基本一致
/// 4.使用HashTable保存 key 和 value 时,不要将同一个数值存入key和value,否则将导致数据顺序输出给数组
/// 注意:
/// 生成时间和具体使用的PC几性能有关,仅做参考!
/// </remarks>
static int[] UseHashTableToNonRepeatedRandom(int length, int minValue, int maxValue)
{
Hashtable hashtable = new Hashtable();
int seed = Guid.NewGuid().GetHashCode();
Random random = new Random(seed);
for (int i = 0; hashtable.Count < length; i++)
{
int nValue = random.Next(minValue, maxValue);
if (!hashtable.ContainsValue(nValue) && nValue != 0)
{
hashtable.Add(i, nValue);
//hashtable.Add(nValue, nValue); // 将 key 和 value设置成一样的值,导致hashtable无法按添加顺序输出数组
//Console.WriteLine(nValue.ToString());
}
}
int[] array = new int[hashtable.Count];
hashtable.Values.CopyTo(array, 0);
return array;
}
/// <summary>
/// 检查生成的随机数据是否重复
/// 如果重复则继续递归调用
/// 如果不重复则返回该数
/// </summary>
/// <param name="arr">生成的非重复随机数数组</param>
/// <param name="temp">随机数</param>
/// <param name="minValue">最大值</param>
/// <param name="maxValue">最小值</param>
/// <param name="random">生成随机数对象</param>
/// <returns>返回一个不重复的随机数</returns>
static int getNumberNonRepeatedRandom(int[] arr, int temp, int minValue, int maxValue, Random random)
{
int n = 0;
while (n <= arr.Length - 1)
{
if (arr[n] == temp) //利用循环判断是否有重复
{
temp = random.Next(minValue, maxValue); //重新随机获取。
getNumberNonRepeatedRandom(arr, temp, minValue, maxValue, random);//递归:如果取出来的数字和已取得的数字有重复就重新随机获取。
}
n++;
}
return temp;
}
/// <summary>
/// 递归,用它来检测生成的随机数是否有重复,如果取出来的数字和已取得的数字有重复就重新随机获取。
/// </summary>
/// <remarks>
/// 使用条件:
/// 1.maxValue - minValue = length。
/// 若差值过于接近或等于length将导致 Stack Overflow Excepion 异常
/// 该异常一般是因为函数调用栈溢出,
/// 也就是函数调用的层次超出了程序能够接受的范围.
/// 这种情况通常是由函数循环调用导致无限递归而引起的
/// 2.实际测试生成1w个数 maxValue 是 length 的
/// 2倍及以上:生成时间基本一致
/// </remarks>
static int[] RecursiveMethodToNonRepeatedRandom(int length, int minValue, int maxValue)
{
int seed = Guid.NewGuid().GetHashCode();
Random random = new Random(seed);
int[] array = new int[length];
int temp = 0;
for (int i = 0; i < length; i++)
{
temp = random.Next(minValue, maxValue); // 随机取数
array[i] = getNumberNonRepeatedRandom(array, temp, minValue, maxValue, random); // 取出值赋到数组中
}
return array;
}
/// <summary>
/// 方法一 使用随机抽取数组index中的数,填充在新的数组array中,使数组array中的数是随机的
/// 方法一思路:用一个数组来保存索引号,先随机生成一个数组位置,然后把随机抽取到的位置的索引号取出来,
/// 并把最后一个索引号复制到当前的数组位置,然后使随机数的上限减一,具体如:先把这100个数放在一个数组内,
/// 每次随机取一个位置(第一次是1-100,第二次是1-99,...),将该位置的数用最后的数代替。
/// </summary>
/// <remarks>
/// 使用条件:
/// 1.生成上w条不重复随机数据时,建议使用该方法;
/// 2.该方法生成的不重复随机数,参数length 和 maxValue 没有相互的限制关系
/// </remarks>
static int[] UseDoubleArrayToNonRepeatedRandom(int length, int minValue, int maxValue)
{
int seed = Guid.NewGuid().GetHashCode();
Random radom = new Random(seed);
int[] index = new int[length];
for (int i = 0; i < length; i++)
{
index[i] = i + 1;
}
int[] array = new int[length]; // 用来保存随机生成的不重复的数
int site = length; // 设置上限
int idx; // 获取index数组中索引为idx位置的数据,赋给结果数组array的j索引位置
for (int j = 0; j < length; j++)
{
idx = radom.Next(0, site - 1); // 生成随机索引数
array[j] = index[idx]; // 在随机索引位置取出一个数,保存到结果数组
index[idx] = index[site - 1]; // 作废当前索引位置数据,并用数组的最后一个数据代替之
site--; // 索引位置的上限减一(弃置最后一个数据)
}
return array;
}
static void Print(int[] array)
{
for (int i = 0; i < array.Length; i++)
Console.Write(string.Format("{0} ", array[i]));
Console.WriteLine();
}
static void Main(string[] args)
{
int arrayNum = 10000;
int minValue = 1;
int maxValue = arrayNum * 100;
Stopwatch sw = new Stopwatch();
sw.Start();
int[] array1 = UseHashTableToNonRepeatedRandom(arrayNum, minValue, maxValue + 1);
//Print(array1);
sw.Stop();
TimeSpan ts = sw.Elapsed;
Console.WriteLine("使用HashTable总共花费{0}ms.", ts.TotalMilliseconds);
sw.Reset();
sw.Start();
int[] array2 = RecursiveMethodToNonRepeatedRandom(arrayNum, minValue, maxValue + 1);
//Print(array2);
sw.Stop();
ts = sw.Elapsed;
Console.WriteLine("使用Recursion总共花费{0}ms.", ts.TotalMilliseconds);
sw.Reset();
sw.Start();
int[] array = UseDoubleArrayToNonRepeatedRandom(arrayNum, minValue, maxValue);
//Print(array);
sw.Stop();
ts = sw.Elapsed;
Console.WriteLine("使用DoubleArray创建不重复随机数总共花费{0}ms.", ts.TotalMilliseconds);
sw.Reset();
Console.ReadLine();
}
}
}
参考链接:
C# Random 生成不重复随机数
c# Random太快产生的随机数会重复
C# 产生真随机数(RNGCryptoServiceProvider)
C#生成随机数的三种方法