C语言 生成随机数 srand用法 伪随机函数rand srand需不需要重新播种问题 srand该不该放在循环里

初接触 rand() 函数

  之前学习了三种排序算法:1.选择排序;2.冒泡排序; 3.插入排序;用三种算法分别对数组里的元素进行排序。一开始对数组初始化时是使用 0 ~ n升序赋值,然后降序排序成 n ~ 0,后来觉得差点意思,为了体现 “真实性”,决定使用 rand() 对数组进行赋值。

  这时还不知道 rand() 是伪随机,只知道它可以 “随机” 数,后来慢慢了解到 rand() 在调用之前需要初始化种子 srand(seed) (就算不初始化,系统也会默认种子 seed 为 1 ),而种子函数 srand() 其实是多个 “随机数序列” 的合集,所以当种子 seed 确定时, 每次程序启动,调用 rand() 所返回的 “随机数序列” 也是确定的。

  相同种子下rand()返回的随机数序列

  例如编写一个程序 randTest

#include <stdio.h>
#include <stdlib.h>

int main()
{
	short i;

    srand(1);
    for(i=0; i<1000; i++)
        printf("%d, ", rand());

    return 0;
}

  
  每次编译、运行程序,得到的结果都是相同的。尽管计算机重启、关机,都不会改变种子 seed 为 1 的 rand() 的输出结果:

41, 18467, 6334, 26500, 19169, 15724, 11478, 29358, 26962, 24464, 5705, 28145, 23281, 16827, 9961, 491, 2995, 11942, 4827, 5436, 32391, 14604, 3902, 153, 292, 12382, 17421, 18716, 19718, 19895, 5447, 21726, 14771, 11538, 1869, 19912, 25667, 26299, 17035, 9894, 28703, 23811, 31322, 30333, 17673, 4664, 15141, 7711, 28253, 6868, 25547, 27644, 32662, 32757, 20037, 12859, 8723, 9741, 27529, 778, 12316, 3035, 22190, 1842, 288, 30106, 9040, 8942, 19264, 22648, 27446, 23805, 15890, 6729, 24370, 15350, 15006, 31101, 24393, 3548, 19629, 12623, 24084, 19954, 18756, 11840, 4966, 7376, 13931, 26308, 16944, 32439, 24626, 11323, 5537, 21538, 16118, 2082, 22929, 16541, 4833, 31115, 4639, 29658, 22704, 9930, 13977, 2306, 31673, 22386, 5021, 28745, 26924, 19072, 6270, 5829, 26777, 15573, 5097, 16512, 23986, 13290, 9161, 18636, 22355, 24767, 23655, 15574, 4031, 12052, 27350, 1150, 16941, 21724, 13966, 3430, 31107, 30191, 18007, 11337, 15457, 12287, 27753, 10383, 14945, 8909, 32209, 9758, 24221, 18588, 6422, 24946, 27506, 13030, 16413, 29168, 900, 32591, 18762, 1655, 17410, 6359, 27624, 20537, 21548, 6483, 27595, 4041, 3602, 24350, 10291, 30836, 9374, 11020, 4596, 24021, 27348, 23199, 19668, 24484, 8281, 4734, 53, 1999, 26418, 27938, 6900, 3788, 18127, 467, 3728, 14893, 24648, 22483, 17807, 2421, 14310, 6617, 22813, 9514, 14309, 7616, 18935, 17451, 20600, 5249, 16519, 31556, 22798, 30303, 6224, 11008, 5844, 32609, 14989, 32702, 3195, 20485, 3093, 14343, 30523, 1587, 29314, 9503, 7448, 25200, 13458, 6618, 20580, 19796, 14798, 15281, 19589, 20798, 28009, 27157, 20472, 23622, 18538, 12292, 6038, 24179, 18190, 29657, 7958, 6191, 19815, 22888, 19156, 11511, 16202, 2634, 24272, 20055, 20328, 22646, 26362, 4886, 18875, 28433, 29869, 20142, 23844, 1416, 21881, 31998, 10322, 18651, 10021, 5699, 3557, 28476, 27892, 24389, 5075, 10712, 2600, 2510, 21003, 26869, 17861, 14688, 13401, 9789, 15255, 16423, 5002, 10585, 24182, 10285, 27088, 31426, 28617, 23757, 9832, 30932, 4169, 2154, 25721, 17189, 19976, 31329, 2368, 28692, 21425, 10555, 3434, 16549, 7441, 9512, 30145, 18060, 21718, 3753, 16139, 12423, 16279, 25996, 16687, 12529, 22549, 17437, 19866, 12949, 193, 23195, 3297, 20416, 28286, 16105, 24488, 16282, 12455, 25734, 18114, 11701, 31316, 20671, 5786, 12263, 4313, 24355, 31185, 20053, 912, 10808, 1832, 20945, 4313, 27756, 28321, 19558, 23646, 27982, 481, 4144, 23196, 20222, 7129, 2161, 5535, 20450, 11173, 10466, 12044, 21659, 26292, 26439, 17253, 20024, 26154, 29510, 4745, 20649, 13186, 8313, 4474, 28022, 2168, 14018, 18787, 9905, 17958, 7391, 10202, 3625, 26477, 4414, 9314, 25824, 29334, 25874, 24372, 20159, 11833, 28070, 7487, 28297, 7518, 8177, 17773, 32270, 1763, 2668, 17192, 13985, 3102, 8480, 29213, 7627, 4802, 4099, 30527, 2625, 1543, 1924, 11023, 29972, 13061, 14181, 31003, 27432, 17505, 27593, 22725, 13031, 8492, 142, 17222, 31286, 13064, 7900, 19187, 8360, 22413, 30974, 14270, 29170, 235, 30833, 19711, 25760, 18896, 4667, 7285, 12550, 140, 13694, 2695, 21624, 28019, 2125, 26576, 21694, 22658, 26302, 17371, 22466, 4678, 22593, 23851, 25484, 1018, 28464, 21119, 23152, 2800, 18087, 31060, 1926, 9010, 4757, 32170, 20315, 9576, 30227, 12043, 22758, 7164, 5109, 7882, 17086, 29565, 3487, 29577, 14474, 2625, 25627, 5629, 31928, 25423, 28520, 6902, 14962, 123, 24596, 3737, 13261, 10195, 32525, 1264, 8260, 6202, 8116, 5030, 20326, 29011, 30771, 6411, 25547, 21153, 21520, 29790, 14924, 30188, 21763, 4940, 20851, 18662, 13829, 30900, 17713, 18958, 17578, 8365, 13007, 11477, 1200, 26058, 6439, 2303, 12760, 19357, 2324, 6477, 5108, 21113, 14887, 19801, 22850, 14460, 22428, 12993, 27384, 19405, 6540, 31111, 28704, 12835, 32356, 6072, 29350, 18823, 14485, 20556, 23216, 1626, 9357, 8526, 13357, 29337, 23271, 23869, 29361, 12896, 13022, 29617, 10112, 12717, 18696, 11585, 24041, 24423, 24129, 24229, 4565, 6559, 8932, 22296, 29855, 12053, 16962, 3584, 29734, 6654, 16972, 21457, 14369, 22532, 2963, 2607, 2483, 911, 11635, 10067, 22848, 4675, 12938, 2223, 22142, 23754, 6511, 22741, 20175, 21459, 17825, 3221, 17870, 1626, 31934, 15205, 31783, 23850, 17398, 22279, 22701, 12193, 12734, 1637, 26534, 5556, 1993, 10176, 25705, 6962, 10548, 15881, 300, 14413, 16641, 19855, 24855, 13142, 11462, 27611, 30877, 20424, 32678, 1752, 18443, 28296, 12673, 10040, 9313, 875, 20072, 12818, 610, 1017, 14932, 28112, 30695, 13169, 23831, 20040, 26488, 28685, 19090, 19497, 2589, 25990, 15145, 19353, 19314, 18651, 26740, 22044, 11258, 335, 8759, 11192, 7605, 25264, 12181, 28503, 3829, 23775, 20608, 29292, 5997, 17549, 29556, 25561, 31627, 6467, 29541, 26129, 31240, 27813, 29174, 20601, 6077, 20215, 8683, 8213, 23992, 25824, 5601, 23392, 15759, 2670, 26428, 28027, 4084, 10075, 18786, 15498, 24970, 6287, 23847, 32604, 503, 21221, 22663, 5706, 2363, 9010, 22171, 27489, 18240, 12164, 25542, 7619, 20913, 7591, 6704, 31818, 9232, 750, 25205, 4975, 1539, 303, 11422, 21098, 11247, 13584, 13648, 2971, 17864, 22913, 11075, 21545, 28712, 17546, 18678, 1769, 15262, 8519, 13985, 28289, 15944, 2865, 18540, 23245, 25508, 28318, 27870, 9601, 28323, 21132, 24472, 27152, 25087, 28570, 29763, 29901, 17103, 14423, 3527, 11600, 26969, 14015, 5565, 28, 21543, 25347, 2088, 2943, 12637, 22409, 26463, 5049, 4681, 1588, 11342, 608, 32060, 21221, 1758, 29954, 20888, 14146, 690, 7949, 12843, 21430, 25620, 748, 27067, 4536, 20783, 18035, 32226, 15185, 7038, 9853, 25629, 11224, 15748, 19923, 3359, 32257, 24766, 4944, 14955, 23318, 32726, 25411, 21025, 20355, 31001, 22549, 9496, 18584, 9515, 17964, 23342, 8075, 17913, 16142, 31196, 21948, 25072, 20426, 14606, 26173, 24429, 32404, 6705, 20626, 29812, 19375, 30093, 16565, 16036, 14736, 29141, 30814, 5994, 8256, 6652, 23936, 30838, 20482, 1355, 21015, 1131, 18230, 17841, 14625, 2011, 32637, 4186, 19690, 1650, 5662, 21634, 10893, 10353, 21416, 13452, 14008, 7262, 22233, 5454, 16303, 16634, 26303, 14256, 148, 11124, 12317, 4213, 27109, 24028, 29200, 21080, 21318, 16858, 24050, 24155, 31361, 15264, 11903, 3676, 29643, 26909, 14902, 3561, 28489, 24948, 1282, 13653, 30674, 2220, 5402, 6923, 3831, 19369, 3878, 20259, 19008, 22619, 23971, 30003, 21945, 9781, 26504, 12392, 32685, 25313, 6698, 5589, 12722, 5938, 19037, 6410, 31461, 6234, 12508, 9961, 3959, 6493, 1515, 25269, 24937, 28869, 58, 14700, 13971, 26264, 15117, 16215, 24555, 7815, 18330, 3039, 30212, 29288, 28082, 1954, 16085, 20710, 24484, 24774, 8380, 29815, 25951, 6541, 18115, 1679, 17110, 25898, 23073, 788, 23977, 18132, 29956, 28689, 26113, 10008, 12941, 15790, 1723, 21363, 28, 25184, 24778, 7200, 5071, 1885, 21974, 1071, 11333, 22867, 26153, 14295, 32168, 20825, 9676, 15629, 28650, 2598, 3309, 4693, 4686, 30080, 10116, 12249

  
  

重新播种,使得程序每次编译运行产生的随机数不同

  正因为当种子确定时, rand() 返回的 “随机数序列” 也相同,所以才需要在 rand() 前重新播种, 也就是 srand(time(NULL))

  time()函数

   time() 函数包含在头文件 time.h 中,在调用之前需要引入此头文件: #include <time.h>

   time() 函数的函数原型是: time_t time(time_t *seconds)time_t 的大小是 long 的大小(即8字节),可以初始化一个 time_t 数据类型的变量 sec ,存储当前时间,如:time_t sec=time(NULL);

   time(seconds) 返回自1970-01-01 00:00:00 起到参数 seconds 经过的时间,以秒为单位, 返回值为 long 型,参数 seconds 的数据类型也是 long 型。

   time() 函数一般用来求 1970-01-01 00:00:00 到现在的时间(当参数 seconds 为 0 或 ‘NULL’ 时)。

  time()实例

  1. 返回 1970-01-01 00:00:00 到现在(2022-02-11 16:49:20)的天数、小时数、分钟数、秒数:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
	time_t sec;	//也可以写成 long sec;
  	sec=time(NULL);
  	
	printf("1970-01-01 00:00:00 到现在共 %ld天; 共 %ld小时; 共 %ld分钟; 共 %ld秒", sec/86400, sec/3600, sec/60, sec));

    return 0;
}

  
  输出结果:

1970-01-01 00:00:00 到现在共 19034天; 共 456824小时; 共 27409489分钟; 共 1644569360秒
Process returned 0 (0x0)   execution time : 0.011 s
Press any key to continue.

  
  2.利用 void Sleep(DWORD dwMilliseconds) 函数,每隔一秒输出 time(NULL) , Sleep 函数包含在 windows.h 头文件中,参数单位是毫秒,参数类型是 DWORD (double word, 大小两字节, 相当于 short 的大小):

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>

int main()
{
    printf("%ld\n", time(NULL));
    Sleep(1000);
    printf("%ld\n", time(NULL));
    Sleep(1000);
    printf("%ld\n", time(NULL));

    return 0;
}

  
  输出结果:

1644572287
1644572288
1644572289

Process returned 0 (0x0)   execution time : 2.043 s
Press any key to continue.

  srand(time(NULL))

  铺垫了这么多,终于到主角 srand(time(NULL)) ,通过前面的知识我们知道 time(NULL) 会随着时间变化,只要程序不在同一秒内运行两次,则种子就不会相同。种子不同,所产生的随机数序列就不会相同,就不会出现第一个程序 randTest 一样的情况了。

  把程序 randTest 稍作修改,添加头文件 #include <time.h>#include <windows.h>,将种子改成 time(NULL) ,查看两次随机数序列是否一样:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>

int main()
{
	short i;

	/*第一次播种的 rand 序列*/
    srand(time(NULL));
    for(i=0; i<1000; i++)
        printf("%d, ", rand());

	/*休眠一秒后重新播种的 rand 序列*/
	printf("\n\n第二次播种的随机数:\n");
	Sleep(1000);
	srand(time(NULL));
    for(i=0; i<1000; i++)
        printf("%d, ", rand());

    return 0;
}

  从输出结果可以看出两次 srand 产生的随机数序列不相同:
不同种子的rand
  
  

不小心把 srand(time(NULL)) 写进 for 循环里

  经过学习、实践和思考,浅析rand和srand

  尽管知道 srand 对应的是一个个随机数序列,但由于认识、思考的深度不够,错误的认为只有每次 rand 的种子不同,才能做到 “真正的随机”。

  事实上,rand 是伪随机数发生器,它能返回的 “随机数” 的值介于 0 到 RAND_MAX (通过查看 stdlib.h 源码发现,RAND_MAX 被定义为 0x7fff )之间, 而种子函数 srand 里的 “随机数序列” 是根据种子 seed 通过公式演算而来的,“随机数序列” 看起来是 “无序” 的,但它符合一定的规律(因为它是 seed 通过公式演算来的)。

  stdlib.h 源码(任意项目main函数右键 #include <stdlib.h>,点击 Open #include file: ‘stdlib.h’,打开 D:\CodeBlocks\MinGW\x86_64-w64-mingw32\include\stdlib.h):
 stdlib.h 源码
  

  正是因为知道了 srand 是根据 seed 演算来的 “随机数序列”,知道同一个种子下的 rand 是能推算出来的(尽管可能不是人脑能推算的),才想着在每次调用 rand() 时都调用不同的种子,以下是错误示范:

int a[100]={};
char n=100;
char i;

for(i=0; i<n; i++)
{
    srand(time(NULL));
    a[i]=rand();
}

  
  输出结果:

21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 21765 
Process returned 0 (0x0)   execution time : 0.039 s
Press any key to continue.

  

  分析错误

  在现在的我看来,上面的代码有两处错误的地方;
  1. time() 函数不应该放在循环里。
  2. srand() 函数也许不该放在循环里,尽管出发点是想获得更 “随机” 的序列对数组赋值。

  对于问题 1,从输出结果可以看出,整个程序仅用了 0.039s 就运行完了,可见循环 100 次用不了 1 秒,甚至一千次、一万次也不会很久(运行速度也跟硬件有关),而 time() 函数的精度为 1s ,对于想在每次循环里都更换种子,应该使用 Sleep(1000),在每次rand() 后挂起 1s 再才进行下一次播种;或者使用毫秒精度的 GetTickCount() 时间函数、以及微秒精度的 QueryPerformanceFrequency() 时间函数,代替 time(NULL)
  
  对于问题 2,srand 是否不适合放在循环里,从问题 1 出发,更进一步思考,就算使用了 Sleep() 函数、就算使用了精度更高的时间函数,如果循环达到了十万、百万,在每个播种的步骤仍要浪费 0.01s,那么一个十万的循环将在播种这里浪费 1000s,这也太恐怖了。
  回归初心,srand 为什么放在循环里,是因为我觉得 rand 不够 “随机”,我得使用更 “随机、无序” 的方法让 rand 产生的数更 “随机”,那么,就算不考虑时间浪费问题,我把 rand 放在循环里就更随机了吗?更换种子重新 rand 就更随机了吗?

  检验思路:第一步:检验连续种子下的 rand 是否会在相同位置出现相同的数,以此鉴定即使 rand 是伪随机,它也能足够 “随机”。获取 time(NULL) 的值赋值给 long 型变量 sec0,记录种子为 sec0、sec1=sec0+1、sec2=sec0+2、sec3=sec0+3 的 rand 值,分别赋值给长度为 1000 的 int 型数组 a、b、c、d,逐一对比a、b、c、d 的元素是否相同。
  
  第二步:检验在四个种子 sec0、sec1、sec2、sec3 的顺序变化下,rand 出现的值是顺序取 a、b、c、d 第 i 项的值,还是每次刷新种子都从第 0 项开始取,并不会像原本想的那样 “数更随机”。循环里有4个播种函数,种子分别为 sec0、sec1、sec2、sec3,打印 rand 1000 次的结果,对比在记事本里的 a、b、c、d 的值。

  第一步代码:

  1.先获取 sec0 至 sec3 的值:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
    time_t sec0, sec1, sec2, sec3;

    sec0=time(NULL);
    sec1=sec0+1;
    sec2=sec0+2;
    sec3=sec0+3;

    printf("sec0:%ld\nsec1:%ld\nsec2:%ld\nsec3:%ld\n\n",sec0, sec1, sec2, sec3);   //打印种子 sec0、sec1、sec2、sec3 的值
    
    return 0;
}

  
  输出结果:

sec0:1644594655
sec1:1644594656
sec2:1644594657
sec3:1644594658

Process returned 0 (0x0)   execution time : 0.030s
Press any key to continue.

  

  2.将刚才获取的值手动赋值给 sec0 至 sec3,然后开始数组赋值、对比有无重复:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
    int a[1000]={};
    int b[1000]={};
    int c[1000]={};
    int d[1000]={};
    time_t sec0, sec1, sec2, sec3;
    short i;

    sec0=1644594655;
    sec1=1644594656;
    sec2=1644594657;
    sec3=1644594658;

    /*数组 a到d 赋值*/
    srand(sec0);
    for(i=0; i<1000; i++)
        a[i]=rand();

    srand(sec1);
    for(i=0; i<1000; i++)
        b[i]=rand();

    srand(sec2);
    for(i=0; i<1000; i++)
        c[i]=rand();

    srand(sec3);
    for(i=0; i<1000; i++)
        d[i]=rand();

    /*打印 a、b、c、d */
    for(i=0; i<1000; i++)
        printf("%-6d%-6d%-6d%-6d\n", a[i], b[i], c[i], d[i]);

    /*对比有无重复*/
    for(i=0; i<1000; i++)
        if(a[i]==b[i] || a[i]==c[i] || a[i]==d[i] || b[i]==c[i] || b[i]==d[i] || c[i]==d[i])
            printf("在下标 %d 处有相同\n", i);

    return 0;
}

  
  输出结果:(部分截图)
  开头部分:
开头
  

  结尾部分:
结尾  
  由对比结果可以看出,连续的种子,rand 也没有同一下标下重复数字,说明rand 在一定范围下是可以看成是 “随机数”。
  

  第二步代码:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
    time_t sec0, sec1, sec2, sec3;
    short i;

    sec0=1644594655;
    sec1=1644594656;
    sec2=1644594657;
    sec3=1644594658;


    for(i=0; i<1000; i++)
    {
        srand(sec0);
        printf("%-6d", rand());

        srand(sec1);
        printf("%-6d", rand());

        srand(sec2);
        printf("%-6d", rand());

        srand(sec3);
        printf("%-6d", rand());

        printf("\n");
    }

    return 0;
}

  
  输出结果:结果非常明显,这简直就是失败的随机数
第二步
  

  另一种“随机”rand的猜想:

  还有一种猜想就是不停的变换种子:srand(rand() + i + rand()),即使 rand 是取 srand 最开始的数,只要种子在不停的变,rand 到的就不会重复,那是不是也可以看作是 “随机数”?事实上我觉得这样做有点像 “掩耳盗铃 自欺欺人”,明明你种子与上一次程序运行时的种子不一样,你就可以获得一堆程序员设计出的近乎随机的、乱序的数列,你只需要 rand 顺序取出数列里的数就好了,难道每次 “随机” 种子,rand 出来的结果就会更 “随机”?可别忘了 rand 之前 可先得初始化 srand,第一次的 srand(rand() + i + rand()) 必定等价于 srand(41 + i + 41)
  
  

小结

  以上就是最近使用 srand 和 rand 过程中发现的问题啦,不知道有没有说完整,过程中发现 srandom 和 srandom 似乎比 srand 和 rand好用,继续努力!!!

  • 12
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值