热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C/C++编程笔记:C语言rand()随机函数,深入解析程序随机数!

各种编程语言返回的随机数(确切地说是伪随机数)实际上都是根据递推公式计算的一组数值,当序列足够长,这组数值近似满足均匀分布。

各种编程语言返回的随机数(确切地说是伪随机数)实际上都是根据递推公式计算的一组数值,当序列足够长,这组数值近似满足均匀分布。

C的标准函数库提供一随机数生成器rand(定义在stdlib.h),能返回0~RAND_MAX之间均匀分布的伪随机整数(RAND_MAX至少为32767,一般都默认为32767)。

用rand()随机生成在[x,y]内的整数

int k;

k=x+rand()%(y-x+1),k即为所求范围内随机生成的数,rand()%a的结果最大为a-1.

rand( )%20的意思的生成20以内的随机数。

例如:

#include#includevoid main(){for(int i&#61;0;i<10;i&#43;&#43;)printf("%d\n",rand());}

如果我们是第一次运行&#xff0c;而且对其不太清楚&#xff0c;那么它生成的基本上算是0&#xff0d;RAND_MAX之间的等概率随机数列了。但是如果你第二次运行的时候会发现输出结果仍和第一次一样。

原来rand()生成伪随机数时需要一个种子&#xff08;计算伪随机序列的初始数值&#xff09;才行&#xff0c;如果种子相同就会得到相同的序列结果&#xff08;这就是函数的好处T-T&#xff09;。这个“优点”被有的软件利用于加密和解密。加密时&#xff0c;可以用某个种子数生成一个伪随机序列并对数据进行处理&#xff1b;解密时&#xff0c;再利用种子数生成一个伪随机序列并对加密数据进行还原。这样&#xff0c;对于不知道种子数的人要想解密就需要多费些事了。当然&#xff0c;这种完全相同的序列对于你来说是非常糟糕的。要解决这个问题&#xff0c;需要在每次产生随机序列前&#xff0c;先指定不同的种子&#xff0c;这样计算出来的随机序列就不会完全相同了。

srand()用来设置rand()产生随机数时的随机数种子。在调用rand()函数产生随机数前&#xff0c;必须先利用srand()设好随机数种子seed,如果未设随机数种子&#xff0c;rand()在调用时会自动设随机数种子为1&#xff08;有人说默认是0&#xff0c;困惑中&#xff09;。上面的两个例子就是因为没有设置随机数种子&#xff0c;每次随机数种子都自动设成相同值1&#xff0c;进而导致rand()所产生的随机数值都一样。(可能有人知道C语言中的随机函数random&#xff0c;可是random函数并不是ANSIC标准&#xff0c;所以说&#xff0c;random函数不能在gcc,vc等编译器下编译通过。我们可以自己编一个^0^&#xff09;我们需要使程序每一次使用的种子都不一样&#xff0c;现在主要问题是种子srand的选择是不是接近随机&#xff08;不存在完全随机&#xff09;&#xff0c;你也可以人为指定种子数。

Windows9x/NT的游戏FreeCell就允许用户指定种子数&#xff0c;这样用户如果一次游戏没有成功&#xff0c;下次还可以以同样的发牌结果再玩一次。但我们还是喜欢系统自动生成……最简单的方法就是利用系统时间了&#xff08;几乎所有的人都这么做&#xff09;&#xff0c;因为时间的数值随时间变化而变化&#xff0c;运行两次&#xff0c;一般不会出现前一次和后一次相同的局面&#xff0c;time(NULL)会返回一个表示当前系统时间的整数&#xff08;它在time.h中&#xff0c;据说time()函数求出来的是自1970年1月1日到现在的秒数&#xff0c;有的说是unix元年&#xff0c;不知道这两个是不是一天……:&#xff08;&#xff0c;另外有的嫌麻烦会写作time&#xff08;0&#xff09;&#xff09;。我们这么来写&#xff1a;

#include#include#includevoid main(){srand((int)time(0));for(int x&#61;0;x<10;x&#43;&#43;)printf("%d\n",(rand());}

 据说如果软件一直开两天&#xff0c;种子会有1/(60*60*24)个可能会重复&#xff0c;虽说这对于一般人已经绝对足够了&#xff0c;但纵然人不舒服&#xff0c;于是我在网上开到有这么写的&#xff1a;

#include#include#include#includevoid main(void){int i;unsigned int seedVal;struct timeb timeBuf;ftime(&timeBuf);seedVal&#61;((((unsigned int)timeBuf.time&0xFFFF)&#43;(unsigned int)timeBuf.millitm)^(unsigned int)timeBuf.millitm);srand((unsigned int)seedVal);for(i&#61;0;i<10;&#43;&#43;i)printf("%6d\n",rand());}

“上面的程序先是调用_ftime()来检查当前时间&#xff0c;并把它的值存入结构成员timeBuf.time中&#xff0c;当前时间的值从1970年1月1日开始以秒计算。在调用了_ftime()之后&#xff0c;在结构timeBuf的成员millitm中还存入了当前那一秒已经度过的毫秒数&#xff0c;但在DOS中这个数字实际上是以百分之一秒来计算的。然后&#xff0c;把毫秒数和秒数相加&#xff0c;再和毫秒数进行异或运算。当然也可以对这两个结构成员进行更多的计算&#xff0c;以控制seedVal的取值范围&#xff0c;并进一步加强它的随机性&#xff0c;但上例用的逻辑运算就已经足够了。”

看下面代码&#xff1a;

void main(){for(int i&#61;0;i<100000;i&#43;&#43;){srand( (unsigned)time( NULL ) );printf&#xff08;"%d\n",rand());}}

为什么总是生成一个数&#xff1f;&#xff1f;&#xff1f;因为此程序产生一个随机数之前&#xff0c;都调用一次srand&#xff0c;而由于计算机运行很快&#xff0c;所以每用time得到的时间都是一样的&#xff08;time的时间精度较低&#xff0c;只有55ms&#xff09;。这样相当于使用同一个种子产生随机序列&#xff0c;所以产生的随机数总是相同的。把srand放在循环外看看&#xff1a;

srand( (unsigned)time( NULL ) );

for(int i&#61;0;i<100000;i&#43;&#43;)

问题解决了&#xff1a;&#xff09;

既然生成的是

0&#xff0d;RAND_MAX之间均匀分布的随机整数&#xff08;我们姑且把以上方法生成的是理想的随机数吧&#xff09;&#xff0c;那么要生成0-x之间(包括0不包括x)的随机数就把rand&#xff08;&#xff09;改成 

rand()/(double)RAND_MAX*x &#xff0c;要生成x-y之间&#xff08;包括x不包括y&#xff09;的就是 

rand()/(double)RAND_MAX*(y-x)&#43;x   了。但是如果要生0-10之间的整数很多人会这么写&#xff1a;

#include#include#includevoid main(){srand((int)time(0));for(int x&#61;0;x<10;x&#43;&#43;)printf("%d\n",(rand()%10);}

x-y的就是 rand()%(y-x)&#43;x

???懂一点概率的就知道这样写&#xff0c;会使得到的数列分布不均的&#xff0c;但是大家确实都喜欢这么写……因为在x的值比较小&#xff0c;RAND_MAX相对较大&#xff0c;而生成的数列有不算太大&#xff0c;又是用来解决精确度要求不高的问题&#xff08;如一些游戏目标,传说游戏中踩地雷式的遇敌就是用rand()实现的.

当主角在地图上走的时候动不动冒出三两小兵挑衅兼找死.它的实现方式是设主角所立位置为0&#xff0c;主角每走一步&#xff0c;变量加1&#xff0c;当变量&#61;&#61;随机取得的数时&#xff0c;小兵出现&#xff09;&#xff0c;这样写足够了……

下面再讨论生成m个小于n的不重复随机数的算法

本人认为算法结构很重要&#xff0c;但语句结构也很重要&#xff0c;所以我喜欢一个算法给出多个语句结构……

最容易想到的傻瓜算法&#xff0c;逐个产生这些随机数&#xff0c;每产生一个&#xff0c;都跟前面的随机数比较&#xff0c;如果重复&#xff0c;就重新产生&#xff1a;

算法一&#xff08;1&#xff09;

srand((unsigned)time(NULL));for(j &#61; 0; j

很早的时候学编程都喜欢用goto语句&#xff0c;因为过去算法是用流程图表示的&#xff0c;用goto可以直接转译&#xff0c;而且循环语句发展不完善&#xff0c;仅仅是作为条件分支的一个补充&#xff0c;如果条件分支箭头向上就是一个循环&#xff0c;而且goto可以实现过去循环所不能实现的结构&#xff0c;形成很巧妙的循环交叉。所以过去一般认为有两种结构&#xff0c;顺序和分支&#xff0c;循环是分支的特殊情况。但是break和continue语句使这些结构实现成为可能&#xff0c;后来发现goto的使用会造成维护和调试的许多麻烦&#xff0c;所以循环逐渐代替了goto&#xff0c;使程序更有层次。现在编程不建议用goto了&#xff0c;如果用goto还会被认为编程修养不好……言归正题&#xff0c;把它的goto去掉&#xff1a;

算法一&#xff08;2&#xff09;

srand((unsigned)time(NULL));j&#61;0;while (j{a[j] &#61; rand() % n ;for(i&#61;0;iif(ij&#43;&#43;}

 但是后来都建议用for循环&#xff0c;这样可以使循环条件跟紧凑&#xff1a;

算法一&#xff08;3&#xff09;

srand((unsigned)time(NULL));for(j&#61;0;j

但这是个很笨的方法&#xff0c;且比较次数呈线性增长&#xff0c;越往后次数越多……另一个想法是生成一个就把它从中去掉&#xff0c;就可以实现不重复了&#xff1a;

算法二&#xff08;1&#xff09;

for (i&#61;0;i

b[]是生成的随机数列

 这样做也太麻烦了&#xff0c;生成的直接做个标记不就行了&#xff1f;

算法三&#xff08;1-1&#xff09;

i&#61;rand()%m;a[i]&#61;1;b[0]&#61;i;for(j&#61;1;j

写紧凑一些吧&#xff0c;直接&#xff1a;

算法三&#xff08;1-2&#xff09;

for(j&#61;0;j

但是我看到了更简洁的&#xff1a;

int n&#61;某值&#xff1b;int a[n]&#61;{0}&#xff1b;for (i&#61;1;i<&#61;n;i&#43;&#43;){while(a[x&#61;rand()%n]);a[x]&#61;i;}

这个无循环体的while保证a[m]是初始化的0状态。这生成了n个&#xff0c;我们只取m个&#xff0c;这太浪费了&#xff0c;结合一下&#xff1a;

int n&#61;某值&#xff0c;m&#61;某值&#xff1b;int a[n]&#61;{0},b[m]for (i&#61;1;i<&#61;n;i&#43;&#43;){while(a[x&#61;rand()%n]);b[i]&#61;x;a[x]&#61;1;}

但是总叫人不舒服&#xff0c;对这种算法谁有更好的写法呢&#xff1f;

这种算法越到后面&#xff0c;遇到已使用过的元素的可能性越高&#xff0c;重复次数就越多&#xff0c;但是当m较小时还是很好的&#xff1a;&#xff09;

这都不太让人满意么&#xff1f;看看下面这个&#xff1a;

算法四&#xff08;1&#xff09;

for (i&#61;0;i0;i--){w&#61;rand()%i;t&#61;a[i];a[i]&#61;a[w];a[w]&#61;t;}

这个算法很不错&#xff0c;有人会怀疑其随机性&#xff0c;但个人认为是没问题的&#xff0c;首先第二行按顺序用0到n填满整个数组&#xff1b;第三行&#xff0c;是随机产生从0到n-2个数组下标&#xff0c;把这个下标的元素值跟n-1下标的元素值交换&#xff0c;一直进行到下标为1的元素。因此它只需要遍历一次就能产生全部的随机数。

但这样会生成n个小于n的随机数&#xff0c;我们只要m个&#xff0c;再加两句&#xff1a;

for(i&#61;0;i

 b[i]&#61;a[i];

b[]是生成的随机数列

 如果m和n很接近的话或者相等是最好&#xff0c;但可能不是……那改一下&#xff1a;

算法四&#xff08;2&#xff09;

for (i&#61;0;i

但是条件反过来了&#xff0c;如果m远小于n还行&#xff0c;如果接近&#xff0c;其随机性就存在问题了&#xff08;为什么&#xff1f;&#xff09;&#xff0c;再改一下&#xff1a;

算法四&#xff08;3&#xff09;

for (i&#61;0;i

这样可以万无一失了……

希望对大家有帮助&#xff01;

自学C/C&#43;&#43;编程难度很大&#xff0c;不妨和一些志同道合的小伙伴一起学习成长&#xff01;

C语言C&#43;&#43;编程学习交流圈子&#xff0c;【点击进入】微信公众号&#xff1a;C语言编程学习基地

有一些源码和资料分享&#xff0c;欢迎转行也学习编程的伙伴&#xff0c;和大家一起交流成长会比自己琢磨更快哦&#xff01;


推荐阅读
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 本文详细介绍了C语言中的指针,包括其基本概念、应用场景以及使用时的优缺点。同时,通过实例解析了指针在内存管理、数组操作、函数调用等方面的具体应用,并探讨了指针的安全性问题。 ... [详细]
  • 本文详细介绍了C语言的起源、发展及其标准化过程,涵盖了从早期的BCPL和B语言到现代C语言的演变,并探讨了其在操作系统和跨平台编程中的重要地位。 ... [详细]
  • 俗话说得好,“工欲善其事,必先利其器”。这句话不仅强调了工具的重要性,也提醒我们在任何项目开始前,准备合适的工具至关重要。本文将介绍几款C语言编程中常用的工具,帮助初学者更好地选择适合自己学习和工作的编程环境。 ... [详细]
  • 本文详细解析了muduo库中的Socket封装及字节序转换功能。主要涉及`Endian.h`和`SocketsOps.h`两个头文件,以及`Socket.h`和`InetAddress.h`类的实现。 ... [详细]
  • 本文将介绍如何使用 Go 语言编写和运行一个简单的“Hello, World!”程序。内容涵盖开发环境配置、代码结构解析及执行步骤。 ... [详细]
  • 题目描述:给定n个半开区间[a, b),要求使用两个互不重叠的记录器,求最多可以记录多少个区间。解决方案采用贪心算法,通过排序和遍历实现最优解。 ... [详细]
  • 数据库内核开发入门 | 搭建研发环境的初步指南
    本课程将带你从零开始,逐步掌握数据库内核开发的基础知识和实践技能,重点介绍如何搭建OceanBase的开发环境。 ... [详细]
  • CMake跨平台开发实践
    本文介绍如何使用CMake支持不同平台的代码编译。通过一个简单的示例,我们将展示如何编写CMakeLists.txt以适应Linux和Windows平台,并实现跨平台的函数调用。 ... [详细]
  • Google最新推出的嵌入AI技术的便携式相机Clips现已上架,旨在通过人工智能技术自动捕捉用户生活中值得纪念的时刻,帮助人们减少照片数量过多的问题。 ... [详细]
  • 本文详细介绍了C语言中链表的两种动态创建方法——头插法和尾插法,包括具体的实现代码和运行示例。通过这些内容,读者可以更好地理解和掌握链表的基本操作。 ... [详细]
  • 深入理解动态链接库及其应用
    本文将探讨动态链接库的基本概念,包括Windows下的动态链接库(DLL)和Linux下的共享库(Shared Library),并详细介绍如何在Linux环境中创建和使用这些库。 ... [详细]
  • C语言实现推箱子游戏的完整代码
    本文详细介绍了如何使用C语言在Linux环境下实现一个简单的推箱子游戏,包括游戏的基本规则、地图设计及代码实现。适合C语言初学者学习。 ... [详细]
  • 如何在Linux中实现字符设备控制
    本文详细探讨了在Linux环境下控制字符设备的方法,包括蜂鸣器和模数转换器(ADC)的实际操作案例。对于开发者来说,了解这些基础知识对于嵌入式系统的开发尤为重要。 ... [详细]
  • iTOP4412开发板QtE5.7源码编译指南
    本文详细介绍了如何在iTOP4412开发板上编译QtE5.7源码,包括所需文件的位置、编译器设置、触摸库编译以及QtE5.7的完整编译流程。 ... [详细]
author-avatar
快乐皇妃800
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有