在区间 [ m, n ] 随机取值,首先应判断 n > m 是否成立!下面的代码没有判断,因为文章仅在 n > m 的情况下,从算法、技术方面实现随机取数
最简单的代码:
#include
using namespace std;
int main()
{
srand(time(NULL));
int i;
i = rand();
cout << "i = " << i << endl <<endl;
system("pause");
return 0;
}
rand()详解:https://www.cnblogs.com/cyttina/p/3162208.html
用递推公式,得到周期很长的数列 ( 开始的数定了,那数列的数就固定下来 ),至于 rand() 内部递推公式是什么,网上没找到。
递推开始的数就很重要了!
srand( 数字p ); 表示递推从带入 p 开始,意味着:无论程序重复运行几次,每次得到的数列都是一模一样的(易知)
srand(time(NULL)) ; 表示 开始的位置 是用系统的时间,而时间是每分每秒都在变化的,这样就得到了 “ 随机 ” 数列,即随机数,仔细想想依然不够随机…,这一点见 本文1.4.4(2)
必须是 在 主函数 中加上 srand(time(NULL)); 而不是在子函数的第一行! 这样 rand() 就是随机 整数。
在子函数中想随机生成 整数,同样可以用 rand()
可参见 https://blog.csdn.net/qq_40893824/article/details/105066291
rand() 整数的随机取值范围:[ 0, 32767 ],RAND_MAX = 32768的余数就是 [ 0, 32767 ]
适用范围 | m 和 n 的区间长度 | 随机取值 |
仅整数 | n – m < 32768 = 2^15 | rand()%(n – m + 1) + m |
整数、浮点数 | n – m >= 32768 = 2^15 | 1.0 * rand() / 32767 * (n – m) + m 或 ( (double)rand() / 32767 ) * (n - m) + m |
1 在n – m >= 32768,随机取值 1.0 * rand() / 32767 * (n – m) + m 中 1.0 不可以省略!
或 ( (double)rand() / 32767 ) * (n - m) + m 中 double 不可省略!
若省略,则随机值 极大概率 会是左边界值 m,极小概率是右边界值 n
因为 rand() 是 [0, 32766] 时 rand() / 32767 = 0!
rand() 是32767 时,rand() / 32767 = 1
2 代码中需注意:
生成浮点数,使用了数据类型 long double,在用 printf() 输出时,输出格式是%-3.4Lf,L是大写不是小写!
float | double | long double | |
---|---|---|---|
输入scanf() | %-3.4f | %-3.4lf | %-3.4Lf |
输出printf() | %-3.4f | %-3.4f | %-3.4Lf |
1 在区间 [ m, n ] 生成整数
1.1 区间范围 < 32768,rand()%(n – m + 1) + m
1.2 区间范围 >= 32768,1.0 * rand() / 32767 * (n – m) + m
2 在区间 [ m, n ] 生成浮点数:1.0 * rand() / 32767 * (n – m) + m
#include
using namespace std;
void creat()
{
long long m, n, p, i, length;
long double m1, n1, p1;
cout << "1 区间取整数" << endl << endl;
cout << "1.1 n - m < 32768" <<endl;
m = -2113;
n = 3567;
cout << "m = " << m << endl;
cout << "n = " << n << endl;
cout << "在[" << m << ", " << n << "]中随机取整数:" << endl;;
for(i = 0; i < 10; i++)
{
p = rand()%(n - m + 1) + m;
cout << "第" << setw(2) << i + 1 << " 次:p = " << p << endl;
}
cout << endl << endl;
cout << "1.2 n - m >= 32768" <<endl;
m = -21133;
n = 35673;
cout << "m = " << m << endl;
cout << "n = " << n << endl;
cout << "在[" << m << ", " << n << "]中随机取整数:" << endl;
for(i = 0; i < 10; i++)
{
p = (1.0*rand()/32767)*(n - m) + m;
cout << "第" << setw(2) << i + 1 << " 次:p = " << p << endl;
}
cout << endl << endl << endl;
cout << "2 区间取浮点数" << endl << endl;
cout << "rand()直接取" << endl;
m1 = -21131234.2123123;
n1 = 35671234.2121231;
cout << "m1 = " << m1 << endl;
cout << "n1 = " << n1 << endl;
cout << "在[" << m1 << ", " << n1 << "]中随机取浮点数:" << endl;;
for(i = 0; i < 10; i++)
{
p1 = ( (double)rand()/32767 )*(n1 - m1) + m1;
cout << "第" << setw(2) << i + 1 << " 次:p1 = " ;
printf("%-7.8Lf\n", p1);
}
cout << endl << endl << endl;
}
int main()
{
srand(time(NULL));
int i;
i = rand();
cout << "随机i = " << i << endl << endl << endl;
creat();
system("pause");
return 0;
}
随机取整数 和 浮点数,当取值范围特别大时,会失去 随机的均匀性,即:区间中有些数是 不可能 被取到的!
原因?
再来研究随机取值的思路:
讨论范围:在区间 [ m, n ] 中 随机取值 p,n - m >= 32768,p = 1.0 * rand() / 32767 * (n – m) + m 看成 [ m, n ] 的随机值,p 可能的取值有哪些?
本质上,rand()随机在 [ 0, 32767 ] 中随机取数,有 32768 种情况,扩展到 [ m, n ] 其实仍然有32768种情况(易知)
[ 0, 32767 ] 的 323768 种情况是:0, 1, 2, …, 32766, 32767
[ 0, 32767 ] 和 [ m, n ] 的 323768 种情况比较:
情况编号 | rand() 随机数p | [ m, n ] 随机数p |
---|---|---|
1 | 0 | m |
2 | 1 | m + (n - m) / 32767 |
3 | 2 | m + 2 * (n - m) / 32767 |
4 | 3 | m + 3*(n - m)/32767 |
… | … | … |
32767 | 32766 | m + 32766(n - m)/32767 |
32788 | 32767 | m + 32767(n - m)/32767 = n |
绝对不会取到的数的个数 = n - m + 1 - 32767 = n - m - 32768
当 n - m 超过 32768 不是很多时,未取到的数很少(姑且可忽略),其随机生成的数还可以看成随机数
但是当 n - m 远超 32768 时,随机的数就不能当成随机数了!
下面的1.3改进了这个不足!
均匀性:每个整数都会被取到,且概率相同
在 [ m, n ] 中随机生成整数,区间长度 length = n - m
然后在 [ 0, length ] 中随机取数 p,最后再加上 m,这样就得到 [ m, n ] 的随机整数,怎么随机取整数 又不失 均匀性?
A length < 32768 时,返回 rand() ; 而不是返回 rand()%(length+1);
B 否则
r = rand() ;
p = ( 1.0 * r / 32767 ) * length ;
r 分 2 种情况
(1) 当取 0 - 32766,相邻两数的间隔是 length = length / 32767 , 递归 至 A,p += 递归返回的数
(2) 当取 32767,这个数就是边界 length,返回 p
代码在(1) 后,不管是不是 32767 都要 return p; 因为这时可以看成 p 已经是生成好的数了,需要返回。
这样可保证 [ 0, length ] 中每个 整数 都可能 被取到。
即,保证了 [ m, n ] 中每个 整数 都可能 被取到
注意:
1 长度 比 区间包含数字的个数 小 1
2 区间开闭
上面两点在编程时很容易混淆,不知道该除以长度还是数字的个数
闭区间 除以 长度,此时端点会被取到,容易思考;
若除以包含的数字个数,每次随机取数要额外考虑最后一段的取值范围,麻烦 又 容易出错
1 [ m, n ] 随机生成浮点数,m 和 n 本身也是浮点数,区间 length = n - m 是浮点数
将 length 强制转为 long long 类型,再随机取整数,小数部分单独写个过程随机取
如何取整 或 强制取整 见文章:https://blog.csdn.net/qq_40893824/article/details/105922443
2 怎么取?在什么范围内随机取?
因为整数部分已经随机取了,而且 每个整数都有被取到的可能,那么现在的小数,它的范围应该在 [ 0, 1) 之间,注意开闭!
但有一种情况不是 [ 0, 1) 间取值
举例这样描述这个情况:length = 12.345,整数就在随机在 [ 0, 12 ] 中取值,当取到12 时,我的小数部分就不能在 [ 0, 1) 间取值,因为可能会超过边界,比如小数取到 0.6,则12 + 0.6 = 12.6 > 12.345,越界了,应在 [ 0, 0.345 ] 中取浮点数
此时定义 r 是取到的整数
当 length - r < 1时,在 [ 0, length - r ] 中取值 (1.0 * rand()) / 32767 * ( length - r )
当 length - r >= 1时,在 [ 0, 1) 中取值 (1.0 * rand()) / 32768 ;
注意:
上面除的数 32767 和 32778 不一样
因为是 2 种情况:32767 对应闭区间,32768 对应开区间!
为什么 length - r >= 1时,在 [ 0, 1) 中取值 而不是 [ 0, 1 ] 中取值?
原因:若 区间边界1 可能被取到,那 此时的数就不是小数了,整数平均被取到的均匀性会破坏掉
#include
#define min 1e-4
using namespace std;
long long LLmyrand(long long length)
{
long long r, p;
if(length < 32768)
return rand()%(length+1);
else
{
r = rand();
p = (1.0*r/32767)*length;
//cout << "粗略 p - m = " << p << endl;
if (r < 32767)
{
length /= 32767; // length被分了32767份, length/32767 就是每一份的长度;
//注意:
// a 长度 比 区间包含数字 的个数小 1
// b 区间的开闭
// a、b 两点在编程时很容易混淆,不知道该除以长度还是数字的个数
// 闭区间 除以 长度,此时端点会被取到,容易思考;若除以包含的数字个数,每次随机取数要额外考虑最后一段的取值范围,这样不好
p += LLmyrand(length);
}
return p;
}
}
long double LDmyrand(long double length)
{
long long r;
long double p, leng;
r = LLmyrand((long long)length);
//cout << "粗略 p - m = " << p << endl;
p = (long double)r ;
leng = length - r;
if(leng >= 1)
{
p += (1.0*rand())/32768;
}
else
{
p += (1.0*rand())/32767*leng;
}
return p;
}
void creat()
{
long long m, n, p, i, length;
long double m1, n1, p1, length1;
cout << "n - m > 32768 生成整数 - 改进" << endl << endl;
m = -211123234232;
n = 331123234234;
cout << "m = " << m << endl;
cout << "n = " << n << endl;
cout << "在[" << m << ", " << n << "]中随机取整数:" << endl << endl;
for(i = 0; i < 10; i++)
{
length = n - m;
p = LLmyrand(length) + m;
//cout << "p - m = " << p - m << endl;
cout << "第" << setw(2) << i + 1 << " 次:p = " << p << endl << endl;
}
cout << endl << endl << endl;
cout << "n1 - m1 > 32768 生成浮点数 - 改进" << endl << endl;
m1 = -1231212312.123123123;
n1 = 1231567358.1212;
cout << "m1 = " << setprecision(20) << m1 << endl;
cout << "n1 = " << setprecision(20) << n1 << endl;
cout << "在[" << setprecision(20) << m1 << ", " << n1 << "]中随机取浮点数:" << endl << endl;
for(i = 0; i < 10; i++)
{
length1 = n1 - m1;
p1 = LDmyrand(length1) + m1;
//cout << "p1 - m1 = " << p1 - m1 << endl;
cout << "第" << setw(2) << i + 1 << " 次:p1 = " << setprecision(20) <<p1 << endl << endl;
}
cout << endl << endl << endl;
}
int main()
{
srand(time(NULL));
int i;
i = rand();
creat();
system("pause");
return 0;
}
1.3 中,在整数区间的范围内随机取整数
在随机浮点数中,也用到了随机取整数的操作,弥补了随机的均匀性
1.3 中尝试 随机取数的均匀性(每个整数都会被取到,且概率相同)
再看 1.3 的思路:
在 [ m, n ] 中随机生成整数,区间长度 length = n - m
直接在 [ 0, length ] 中随机取数 p,最后再加上 m,这样就得到 [ m, n ] 的随机整数
A length < 32768 时,返回 rand()%length ;
B 否则
r = rand() ;
p = ( 1.0 * r / 32767 ) * length ;
r 共有 32768 种情况
(1) 当取 0 - 32766,相邻两数的间隔是 length = length / 32767 , 递归 至 A,p += 递归返回的数
(2) 当取 32767,这个数就是边界 length,返回 p
有漏洞的地方:
当取 0 - 32766,相邻两数的间隔是 length = length / 32767 , 递归 至 A,p += 递归返回的数
简化模型:在 [ 3, 26 ] 中随机取整数,rand()的范围是 [ 0, 3 ],即RAND_MAX = 3,length = 26 - 3 = 23,在 [ 0, 23 ] 中随机取数
r = rand()
r | p = ( 1.0 * r / 3 ) * 26 ; |
---|---|
0 | 0 |
1 | 7 |
2 | 15 |
3 | 23 |
0, 7, 15, 23 的间隔分别是:7, 8, 8
这说明 相邻两数的间隔不是固定的 length / 32767!
length = (1.0*(r + 1)/32767)*length - (1.0*r/32767)*length;
代码太长,单独写成一篇文章:https://blog.csdn.net/qq_40893824/article/details/105928858
(1) 仍未到达完全的均匀性
(2) rand() 本身的不足限制
每个数会被取到(√),每个数被取到的概率相同(×)
例子 - 简化模型:
在 [ 3, 26 ] 中随机取整数,rand()的范围是 [ 0, 3 ],即RAND_MAX = 3,length = 26 - 3 = 23,在 [ 0, 23 ] 中随机取数
第一次取值可能是:0, 7, 15, 23 (概率相同)
第二次取值概率:
第1次取值 | 概率 | 下一次取值每个数的概率 |
---|---|---|
0 | 1/4 | 1/7 |
7 | 1/4 | 1/8 |
15 | 1/4 | 1/8 |
得:
数字 | 取值概率 |
---|---|
0-6 | ( 1/4 ) * ( 1/7 ) |
7 | ( 1/4 ) * ( 1/8 ) |
15 | ( 1/4 ) * ( 1/8 ) |
23 | 1/4 |
这样看来每个数都可能被取到,但概率不唯一,即仍不均匀
简单验证过程是否有问题,看概率和是否为 1
( 1/4 ) * ( 1/7 ) * 7 +
( 1/4 ) * ( 1/8 ) * 8 +
( 1/4 ) * ( 1/8 ) * 8 +
1/4 = 1
可看看:https://blog.csdn.net/czc1997/article/details/78167705
在没有开始 rand() 时,rand 是随机的,一旦开始,其生成的数就是确定了
因为 rand() 本质是周期很长的数列(递推),给定递推开始的数,那么后面的数都是可以用公式表示出来
以上两点均暂时没想出解决的办法
1个汉字存进 字符数组,貌似是占 1个字节单元,其实是 2个字节单元!汉字有个区位码表
高字节 对应 区码( = 行号)
低字节 对应 位码( = 列号)
区码 + 位码 = 区位码
区位码表(94 x 94 的表格):每个汉字 对应唯一 1个位置
内容见:https://www.doc88.com/p-9955966114086.html ( 无 04 - 15 分区内容)
和 https://wenku.baidu.com/view/b5340aef11661ed9ad51f01dc281e53a580251a2.html ( 含 04 - 09 分区内容 )
为什么不见 10 - 15分区内容?因为:
01 - 09 区 :特殊符号
10 - 15 区 :用户自定义符号(未编码,属于一级汉字范围)
16 - 55 区 :一级汉字(3755 = (55 - 16 + 1 - 1)*94 + 89),按拼音排序(55时,列号范围:[ 1, 89 ] !)
56 - 87 区 :二级汉字(3008 = (87 - 56 + 1)*94),按部首/笔画排序
88 - 94 区 :用户自定义汉字(未编码)
所以汉字共 6763 个
汉字的行号范围 [16, 87],列号范围 [1, 94]
1 个汉字 占 2 个字节( = 16 bit),每 1 个字节 = 8 bit 位
而且每 1 个字节的最高位(符号位) 一定 是1,代表负数!
1 个字母 占 1 个字节,最高位 一定 是 0, 代表正数!
a = rand()%(87 - 16 + 1) + 16; //在[16, 87]随机生成整数
b = rand()%num1 + 1; //在[1, 94]随机生成整数
str[2*i] = 0 - a; // 别忘了是负数!
str[2*i + 1] = 0 - (b); // 同理
本次注意单独列出来,很重要
1 汉字区位码 在计算机中存储
以汉字 ‘山’ 为例
汉字 ‘山’ 的 位码 = 41,位码 = 29
#include
#define num 100
using namespace std;
int main()
{
char b0[num], b1[num];
//
cout << "汉字真正区位码 与 实际存储:" << endl << endl;
char str[num] = "山";
cout << "山: " << endl;
cout << "真正的 区码 = 41, 位码 = 29" << endl;
itoa(str[0], b0, 10);
itoa(str[1], b1, 10);
cout << "计算机中 存储的区码 = "<< b0 << ", 位码 = " << b1 << endl;
cout << "41 - (-55) = " << 41 + 55 << endl;
cout << "29 - (-67) = " << 29 + 67 << endl << endl;;
system("pause");
return 0;
}
2 区码是 55 的时候,位码 范围是 [ 1, 89 ]:
#include
#define num 10000
#define num1 94
using namespace std;
void creat(char *str, FILE *fp)
{
char name[] = "./set.txt";
fp = fopen(name, "w+");
cout << "随机生成汉字" << endl;
int length = rand()%num, i, a, b;
cout << "想生成 " << length << "个字" << endl;
str = (char*)malloc((2*length + 1)*sizeof(char));
for(i = 0; i < length; i++)
{
a = rand()%(87 - 16 + 1) + 16; //在[16, 87]随机生成整数
if(a == 55)
b = rand()%(89 - 1 + 1) + 1; //在[1, 89]随机生成整数
else
b = rand()%num1 + 1; //在[1, 94]随机生成整数
str[2*i] = a - 96; // 别忘了是负数!
str[2*i + 1] = b - 96; // 同理
//printf("%d %d\n",str[2*i] + 96, str[2*i + 1] + 96);
}
str[2*length] = '\0';
cout << "生成完毕!" << endl << endl;
puts(str);
fputs(str,fp);
cout << endl << endl;
fclose(fp);
}
int main()
{
srand(time(NULL));
char str[] = "";
int i;
FILE *fp;
for(i = 0; i < 1; i++)
creat(str, fp);
system("pause");
return 0;
}