算法:如何高效产生m个n范围内的不重复随机数(m<=n)

最近网上看到一道题,如何取100以内不重复的100个随机数?代码如下:

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分钟都没跑完。

好的题目却没有好的答案些不甘心,便继续探索了一下,发现该问题确实不简单,在《 Programming Pearls 》一书中也有提到,题目为“如何高效产生m个n范围内的不重复随机数(m<=n)”,如果还继续使用以上算法,把m、n放大到10000,估计程序1个小时都跑不完,效率令人发指。

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

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步不太容易理解,我带大家分析每一步的具体原理,你马上就能一目了然:

循环:i = 0 , r = 39(假设随机数为该值) ,交换 nums[0] 和 nums[39] 的值。

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

时间: 2024-08-27 18:25:54

算法:如何高效产生m个n范围内的不重复随机数(m<=n)的相关文章

算法:高效的队列deque

一.题目: 有一个序列u,满足: 1. 第一个元素是1 2. 此后任意一个元素x,2x+1和3x+1也必定在u中 现给定整数n,求序列u中的第n个元素是什么?并输出该序列 规定:要注意算法的效率 二.分析 先找几个数计算一下: 1 [1], 3, 4 1, [3], 4, 7, 10 1, 3, [4], 7, 9, 10, 13 其中这个9提醒我们,虽然单纯的2x+1或3x+1一定是递增的,但是前一个数的3x+1有可能大于后一个数的2x+1.因此,当要在序列u中取"下一个数"计算它的

判断一个数是否为质数/素数——从普通判断算法到高效判断算法思路

定义:约数只有1和本身的整数称为质数,或称素数. 计算机或者相关专业,基本上大一新生开始学编程都会接触的一个问题就是判断质数,下面分享几个判断方法,从普通到高效. 1)直观判断法 最直观的方法,根据定义,因为质数除了1和本身之外没有其他约数,所以判断n是否为质数,根据定义直接判断从2到n-1是否存在n的约数即可.C++代码如下: bool isPrime_1( int num ) { int tmp =num- 1; for(int i= 2;i <=tmp; i++) if(num %i==

【算法】高效计算n的m次方

今天看到了一个非常好的算法,数学什么什么定理我不懂,但这算法值得我学习. 目的:计算n的m次方 int power(int n,int m) { int odd=1;//用来把剩下的数乘进去 while(p>1){ if((m & 1) != 0)odd*=n; else n*=n; p/=2; } return n*odd; }

算法心得-高效算法的奥秘(原书第2版)pdf高清版免费下载

下载地址:网盘下载 备用地址:网盘下载 原文地址:https://www.cnblogs.com/hsqdboke/p/9783480.html

【LeetCode-面试算法经典-Java实现】【219-Contains Duplicate II(包含重复元素II)】

[219-Contains Duplicate II(包含重复元素II)] [LeetCode-面试算法经典-Java实现][所有题目目录索引] 代码下载[https://github.com/Wang-Jun-Chao] 原题 Given an array of integers and an integer k, find out whether there are two distinct indices i and j in the array such that nums[i] = n

Java随机数生成原理

一.在j2se里我们可以使用Math.random()方法来产生一个随机数,这个产生的随机数是0-1之间的一个double,我们可以把他乘以一定的数,比如说乘以100,他就是个100以内的随机,这个在j2me中没有. 二.在java.util这个包里面提供了一个Random的类,我们可以新建一个Random的对象来产生随机数,他可以产生随机整数.随机float.随机double,随机long,这个也是我们在j2me的程序里经常用的一个取随机数的方法. 三.在我们的System类中有一个curre

Java获取随机数的3种方法(转)

方法1(数据类型)(最小值+Math.random()*(最大值-最小值+1)) 例: (int)(1+Math.random()*(10-1+1)) 从1到10的int型随数 方法2 获得随机数 for (int i=0;i<30;i++) {System.out.println((int)(1+Math.random()*10));} (int)(1+Math.random()*10) 通过java.Math包的random方法得到1-10的int随机数 公式是:最小值---最大值(整数)的

Java随机数总结

随机数在实际中使用很广泛,比如要随即生成一个固定长度的字符串.数字.或者随即生成一个不定长度的数字.或者进行一个模拟的随机选择等等.Java提供了最基本的工具,可以帮助开发者来实现这一切. 一.Java随机数的产生方式 在Java中,随机数的概念从广义上将,有三种. 1.通过System.currentTimeMillis()来获取一个当前时间毫秒数的long型数字. 2.通过Math.random()返回一个0到1之间的double值. 3.通过Random类来产生一个随机数,这个是专业的Ra

Java获取随机数的3种方法

方法1(数据类型)(最小值+Math.random()*(最大值-最小值+1))例:(int)(1+Math.random()*(10-1+1))从1到10的int型随数 方法2获得随机数for (int i=0;i<30;i++){System.out.println((int)(1+Math.random()*10));}(int)(1+Math.random()*10)通过java.Math包的random方法得到1-10的int随机数公式是:最小值---最大值(整数)的随机数(类型)最小