热门标签 | HotTags
当前位置:  开发笔记 > 人工智能 > 正文

生成不重复的随机数的思路

通常的生成随机数的做法是不考虑重复的,因为即使重复也属于概率意义上的正常情况。但某些情况下需要不重复的随机数据,怎么办呢?我想从大方向上来说,应该只有两个方法。要么牺牲时间要么牺牲空间。

通常的生成随机数的做法是不考虑重复的,因为即使重复也属于概率意义上的正常情况。但某些情况下需要不重复的随机数据,怎么办呢?

我想从大方向上来说,应该只有两个方法。要么牺牲时间要么牺牲空间。

下面均以在101~200的范围内(设为b[100],它实际上是附加空间),从中产生10个不重复的随机数(设为a[10])。

牺牲时间为代价

这种方法不需要附加空间b数组。

要产生一定范围内不可重复的随机数,把曾经生成的随机数保存起来作为历史数据。产生一个新的随机数后在历史数据搜索,若找到就重新产生一个新的再重复数据搜索;否则就认为已经找到了一个新的不同随机数。

可以预见,每个新产生的随机数都要与前面所有的数比较。若重复,舍弃,再产生;否则,产生下一个。平均耗时n的平方量级。

粗看起来,上面的程序似乎没有什么问题,在执行过程中程序也能够通过。但,仔细分析我们就会发现问题出在一个新产生的随机数是否已经存在的判定上。既然是随机数,那么从数学的角度来说在概率上,每次产生的随机数 r就有可能相同,尽管这种可能性很小,但确是一个逻辑性与正确性的问题。因此,每次产生的新的随机数r都有可能是数组random的前i-1个数中的某一个,也就是说程序在运行过程中由此可能会导致死循环!

有人可能会争辩说,这种概率很小嘛,几乎为零。的确,但我要问,算法的五大特性是什么,其中两大特性就是:确定性和有穷性。

所以,怎么解决?牺牲空间。

牺牲空间为代价

以下方法需要附加空间b数组。

将范围数组b[100](b[i]=100+i,不妨设数组下标从1开始)的每个元素设置一个标志位flag。初始均为flag=0;若某元素被选入到a数组中,则flag=1;显然,以后再选到重复元素可以立刻判定是否已选。这不正是以空间换时间吗?

但是仍然有一个很严重的问题,在小规模输入下,无疑它的表现是不错的。但现在举一个失败的例子。

在1~65536之间,选择65500个不重复的随机数。看看后面的随机数,比如第65500个数(最后一个),它要在剩下的36个数中选择才会有flag=0(根本不知道这36个数是什么);哼哼,概率36/65536。越到后面,随机数越难产生,空间也换不了时间。

改进:先在1~65536之间随机选取36个数,删除。将剩下的65500个数依次赋值给a[65500],然后打乱顺序即可。

当范围数组与目标数组的大小非常接近时,上述算法非常有效,建议采用。

仍以最开始的那个例子来说,初始数组b[i]=100+i,a数组空。

每次随机生成数组b的一个下标subscript,然后取出它所对应的数据a[subscript],记下来。然后将数组b的最后一个数b[length]放到下标subscript的位置,同时将数组a长度减1。尽管前若干次生成的下标subscript随机数有可能相同,但,因为每一次都把最后一个数填到取出的位置,因此,相同下标subscript对应的数却绝不会相同,每一次取出的数都不会一样,这样,就保证了算法的确定性、有效性、有穷性。

以生成1-10之间的10个不重复的随机数为例,介绍生成不重复的随机数的一些方法。

通过while循环来实现

通过while循环不停的生成随机数,直到生成一个不重复的为止,这种方法比较容易想到,但是效率也比较低下,实例代码如下:

static void Main(string[] args)
{
	int[] result = new int[10];
	int tmp = -1;
	Random random = new Random();
	bool repeat = false;
	for (int i = 0; i <10; i++)
	{
  		repeat = true;
 		while (repeat)
       	{
     		repeat = false;
        	tmp = random.Next(1, 11);
         	for (int j = 0; j 
  
  	

通过for循环来实现

方法1使用了多处循环嵌套,效率十分低下,所以我应用一定的技巧来减少循环嵌套,来达到提高程序效率的目的。主要思路是如果检测到重复,就把循环变量减1,这样来重新进行一次循环,重新生成一个随机数,直到生成一个不重复的随机数为止,实例代码如下:

static void Main(string[] args)
{
	int[] result = new int[10];
	int tmp = -1;
	Random random = new Random();
	bool repeat = false;
	for (int i = 0; i <10; i++)
	{
  		repeat = false;
    	tmp = random.Next(1, 11);
    	for (int j = 0; j 
  
  	

这个方法减少了一层循环嵌套,效率上有一定的改善。

通过随机排序来实现

这种方法彻底的颠覆了方法1和2的基本思路,先初始化一个包含数字1-10的数组,然后每次循环取一个随机位置,将这个位置的元素和最后一个位置的元素交换!实例代码如下:

static void Main(string[] args)
{
	int[] result = new int[10];
	for (int i = 0; i <10; i++)
   		result[i] = i + 1;
	for (int j = 9; j > 0; j--)
  	{
    	Random r = new Random();
   		int index = r.Next(0, j);
     	int temp = result[index];
    	result[index] = result[j];
     	result[j] = temp;
	}
 	for (int i = 0; i <10; i++)
		Console.WriteLine(result[i].ToString());
}

这种方法消除了循环嵌套,效率上获得了进一步的改善,但是也有一定的限制,如果要生成5组1-10之间的随机数,那这种打乱顺序的方法就无法使用了。

总结

  • 方法1效率比较低下,一般不推荐使用。
  • 方法2比较通用,效率高于方法1,但是效率低于方法3。
  • 方法3虽然效率比较高,但是只能应用与特定的情况下。

下面的例子的主要是思路是,如果检测到已经存在该数字,则将循环数后退一个,重新生成。

void static Main()
{
  Random random = new Random();
  bool repeat = true;
  int temp ;
  int[] store = new int[10];
  for(int i = 0;i <10;i++)
  {
    temp =random.Next(1,11);
    for(int j = 0; j
  
  	

如果所要生成的随机数比较少的话,可以将所有的先存到数组当中,然后再随机交换数组当中的数字即可:

static void Main()
{ 
  int[] store = new store[10];  //定义一个数组,并初始化
  for(int i =1;i<11;i++)      //通过循环将数组进行赋值
  {
    store[i-1] = i;
  }
 
  for(int j = 9;j>0;j--)
  {
    Random r = new Random();
    int index = r.Next(0,j);    //int index = r.Next(0,9);  这个值得商榷,感觉有些问题。
    int temp = store[index]; 
    store[index] = store[j];
    store[j] = temp;
  }
}

本文地址:http://www.nowamagic.net/librarys/veda/detail/510,欢迎访问原出处。


推荐阅读
  • 本文介绍了数据库体系的基础知识,涵盖关系型数据库(如MySQL)和非关系型数据库(如MongoDB)的基本操作及高级功能。通过三个阶段的学习路径——基础、优化和部署,帮助读者全面掌握数据库的使用和管理。 ... [详细]
  • 智能车间调度研究进展
    本文综述了基于强化学习的智能车间调度策略,探讨了车间调度问题在资源有限条件下的优化方法。通过数学规划、智能算法和强化学习等手段,解决了作业车间、流水车间和加工车间中的静态与动态调度挑战。重点讨论了不同场景下的求解方法及其应用前景。 ... [详细]
  • 本文深入探讨了Memcached的内存管理机制,特别是其采用的Slab Allocator技术。该技术通过预分配不同大小的内存块来有效解决内存碎片问题,并确保高效的数据存储与检索。文中详细描述了Slab Allocator的工作原理、内存分配流程以及相关的优化策略。 ... [详细]
  • 华为智慧屏:超越屏幕尺寸的智能进化
    继全球发布后,华为智慧屏于9月26日在上海正式亮相,推出65英寸和75英寸版本。该产品不仅在屏幕尺寸上有所突破,更在性能和智能化方面实现了显著提升。 ... [详细]
  • 本文介绍如何利用栈数据结构在C++中判断字符串中的括号是否匹配。通过顺序栈和链栈两种方式实现,并详细解释了算法的核心思想和具体实现步骤。 ... [详细]
  • Redux入门指南
    本文介绍Redux的基本概念和工作原理,帮助初学者理解如何使用Redux管理应用程序的状态。Redux是一个用于JavaScript应用的状态管理库,特别适用于React项目。 ... [详细]
  • 历经三十年的开发,Mathematica 已成为技术计算领域的标杆,为全球的技术创新者、教育工作者、学生及其他用户提供了一个领先的计算平台。最新版本 Mathematica 12.3.1 增加了多项核心语言、数学计算、可视化和图形处理的新功能。 ... [详细]
  • 机器学习核心概念与技术
    本文系统梳理了机器学习的关键知识点,涵盖模型评估、正则化、线性模型、支持向量机、决策树及集成学习等内容,并深入探讨了各算法的原理和应用场景。 ... [详细]
  • 如何使用Ping命令来测试网络连接?当网卡安装和有关参数配置完成后,可以使用ping命令来测试一下网络是否连接成功。以winXP为例1、打开XP下DOS窗口具体操作是点击“开始”菜 ... [详细]
  • 本文总结了优化代码可读性的核心原则与技巧,通过合理的变量命名、函数和对象的结构化组织,以及遵循一致性等方法,帮助开发者编写更易读、维护性更高的代码。 ... [详细]
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • 本文详细介绍了Hive中用于日期和字符串相互转换的多种函数,包括从时间戳到日期格式的转换、日期到时间戳的转换,以及如何处理不同格式的日期字符串。通过这些函数,用户可以轻松实现日期和字符串之间的灵活转换,满足数据处理中的各种需求。 ... [详细]
  • 1.执行sqlsever存储过程,消息:SQLServer阻止了对组件“AdHocDistributedQueries”的STATEMENT“OpenRowsetOpenDatas ... [详细]
  • 本文将详细介绍多个流行的 Android 视频处理开源框架,包括 ijkplayer、FFmpeg、Vitamio、ExoPlayer 等。每个框架都有其独特的优势和应用场景,帮助开发者更高效地进行视频处理和播放。 ... [详细]
  • 本文详细介绍了PHP中的多条件分支结构,包括if、elseif和else语句的使用方法。通过具体示例,解释了如何根据不同的条件执行相应的代码块,并确保每个条件只能触发一次。 ... [详细]
author-avatar
鸳鸯520_205
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有