最近看到一道算法题,如何取100以内不重复的100个随机数?答案如下:

1
2
3
4
5
6
7
8
9
10
var nums = new int[100];
var list = new List<int>();
var random = new Random();
for (int i = 0; i < 100; i++)
{
int r;
while (list.Contains(r = random.Next(0, 99))) { }
list.Add(r);
nums[i] = r;
}

个人感觉题目很经典,实际生活中会经常遇到类似问题,但答案却如此低效,不免有一些失望,打开vs试了一把,果然2分钟都没跑完。

如果用上面的算法,把m、n放大到10000,估计程序1天都跑不完,效率令人发指。百度一下,发现《编程珠玑》一书收录该算法,题目为“如何高效产生m个n范围内的不重复随机数(m<=n)”。

那么该书中是如何解决该问题的呢?答案如下:

1
2
3
4
5
6
7
8
9
10
11
var nums = new int[100];
var random = new Random();
for (int i = 0; i < 100; i++)
{
nums[i] = i;
}
for (int i = 0; i < 100; i++)
{
var r = random.Next(i, 99);
Swap(ref nums[i], ref nums[r]);
}

该算法非常巧妙的取随机数的位置(数组的下标),替代取随机数本身,每次取到一个随机数之后,就将其在取值范围中排除,下一次仅会在剩下的数字中取,一次遍历就可以完成随机数的选取,效率相当高。


算法详细分析:

  1. 为数组的每个数字按其位置(数组的下标)赋值,我们获得一个 100个数字顺序排列 的数组。
  2. 开始取 i-99 范内的随机数,把每次取到的随机数作为位置(数组的下标)与位置(数组的下标)为 i 的数交换数值。这样做的意义是,将已经取到的随机数在取值范围中排除,下一次去随机数仅会在剩下的数字中取。

第2步不太容易理解,举个栗子:假设第一次取到的随机数是39,把 位置39的数位置0的数 交换之后,再从 位置1 开始看该数组,你会惊奇的发现,剩下的是0-99除39以外的所有数字,但它们的位置是1-99,接下来我们仅需要从1-99中取一个随机数,作为数组下标,即可在剩下的数字中取随机数了,以此类推。


本文最初发表于CSDN博客,现已迁到 石佳劼的博客,感谢CSDN。原文链接