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