通过观察图1,请你思考一个问题,如果你随机地在正方形区域中选择一个点,那么这个被选择的点,也恰巧落在圆形红色区域的概率是多大?这个问题很简单,就是圆面积和正方形面积的比值,简单计算就可以得到这个概率值,应该是 π/4。
也就是说,如果我们做大量的随机实验,最终落在圆内部的次数除以总次数再乘以 4 得到的值,应该接近圆周率 π。随机次数越多,所得到的数值越接近 π。你肯定不喜欢做这种重复的“重体力”劳动,但如果你写好编程,让它帮你做这件事,那就简单容易快捷多了。计算机可是一个不怕辛苦、没有怨言的好帮手,今天就让它来帮助我们完成这个任务吧。
## 必知必会,查缺补漏
思考一下,其实要完成上面这个任务,我们已经具备了一些基础知识,比如说:分支结构(if…else)可以帮助你判断某个点是否在圆内部,循环结构(for/while)可以帮助你完成大量的重复实验。
说到这里,你会发现,面对今天的这个任务,我们还需要做到随机选点,那么这个随机操作,在计算机中应该如何来完成呢?今天我将告诉你的就是程序语言中的随机函数,准备好了么?让我们开始吧。
#### 1.真随机与伪随机
说到随机,就需要说一下真随机与伪随机的概念了。
所谓**真随机**其实并不难理解,我们以掷骰子为例,掷出 1~6 点的概率均为 1/6,如果我问你,上一次掷出的点数是4,那么下一次掷出 6 点的概率是多大?你会发现,依然是 1/6,我们称这两次掷骰子的事件是相互独立的,上一次的结果和下一次之间没有必然联系。
你观察上面这两个数字序列,会发现,第一个序列是123456,这是一个有明显规律的序列,你一定不会觉得这个序列是随机生成的。另一个序列是421635,好像没有什么明显的规律,相比于第一个序列,你是不是更偏向于相信第二个序列是随机生成的序列呢?
第二个序列就是我刚刚所说的伪随机,看起来像是随机序列,可实际上,4后面一定会出现2,2后面一定是1,1后面一定是6,也就是说前一个数字决定了后一个数字。
计算机中究竟如何制造出来这样一个伪随机序列呢,这个问题留到后面的 “动手搞事情” 中,我会使用一行简单的数学公式,制造一个包含100个数字的伪随机数字序列,类似于上图中第二个序列的加大版。
最后你会发现,**所谓计算机中的伪随机数序列**,**就是类似第二个序列那样的,没有什么明显规律的一个规模更大的循环序列。**
现在你知道为什么叫做伪随机了吧,那是因为,一旦要是上一个随机函数的值确定了,下一个数字也就确定了,而纯正意义上的真随机,应该是前后两次出现的数字是两次独立事件,没有任何关系。
#### 2.程序中的随机函数
现在我们所接触到的语言中,没有真随机,全是伪随机。也就是说,语言中给我们准备好了一个随机函数,这个随机函数会根据上一个**随机值**和一个**固定的计算规则**,得到下一个**随机值**。
而你在其他资料中可能会看到**随机种子**这个概念,设置随机种子就是在设置随机函数中记录的上一个随机值。例如,上面我们自己做出来的6个长度的伪随机序列,如果随机种子设置为值1,我们得到的值依次是 635421,如果设置为值 3,那么我们将依次得到 542163。
下面就看看 C 语言中的随机函数的用法吧:
```
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
printf("%d\n", rand() % 1000); // 永远输出固定值
srand(time(0));
printf("%d\n", rand() % 1000); // 每次运行都不同
return 0;
}
```
上面代码中,我们用 rand() 函数,获得一个随机值,这个就是我们前面讲的随机函数,它将依次的返回随机序列中的每一个值。
而 srand() 函数就是设置随机种子的函数,也就是设置随机函数上一次的状态值。time(0) 将返回一个时间戳,你就可以把他当成和当前时间相关的一个整型数字。
你会发现,上面这段程序中,在第 6 行代码里,我们虽然使用了 rand() 函数,可每次运行都将输出同样的值,这是因为我们没有设置随机种子,每次运行时 rand() 函数所记录的起始值都相同,所以每次运行输出的随机值也都相同。
而第 8 行代码中,由于我们根据程序运行时的当前时间设置了随机种子,每次运行程序,第 8 行都将输出不同的值。事实上,如果你在 srand() 函数里面设置一个固定值,每次运行程序,结果也都将是一样的,这个你可以自行尝试。
至此,我们就准备好了今天任务的全部基础知识了,接下来做道练习题,锻炼一下吧。
## 一起动手,搞事情
#### 思考题:设计迷你随机函数
>
设计一个循环过程,循环100次,以不太明显的规律输出 1~100 中的每个数字。
要求1:规律尽量不明显。
要求2:只能使用循环和最基本的运算,不允许超前使用数组。