按照正态分布来点名
抽签系统(正态分布点学号真的是太不公平了!)
学习委员让我按照正态分布来点学生学号 让他们回答问题获得平时分。
那我就先草草的做了一个此程序。。。我也啥也不懂,网上找了些资料,
学习过程稍微记录一下,读者有需要的话随意使用。
这里主要用到原理的是box-muller,基本思想是先得到服从均匀分布的随机数再将服从均匀分布的随机数转变为服从正态分布。
如果在 (0,1] 值域内有两个一致的随机数字 U1 和 U2,
可以使用以下两个等式中的任一个算出一个正态分布的随机数字 Z:
Z = R * cos( θ ) 或 Z = R * sin( θ )
其中, R = sqrt(-2 * ln(U2)), θ = 2 * π * U1
正态值 Z 有一个等于 0 的平均值和一个等于 1 的标准偏差,可使用以下等式将 Z 映射到一个平均值为 m、标准偏差为 sd 的统计量 X:
X = m + (Z * sd)
C代码: (计算机编程中, log函数==ln()函数,以e为底的自然对数, log10 才是以10为底的函数)
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <math.h >
#include <string.h>
const int CLASS_NUM=32;//取值范围为[1,32]
const int cishu=1000;//点名的次数
const int check=5; //每5次点名如果有没被点到一定次数的一定要点他
const int qiwang=16; //期望
const int fangcha=50; //此处为方差,根据正态分布性质,其表示数据的集中程度,越小越集中于期望
int rec[CLASS_NUM+1];
int aver_dian=1; //保证每个人都必须要点到aver_dian次
double gaussrand()
{
static double U, V, y;//定义静态局部变量
static int phase = 0;
double z;
double PI=3.141592654;
if(phase == 0) //产生两个随机数U和V
{
U = rand() / (RAND_MAX + 1.0);
V = rand() / (RAND_MAX + 1.0);
z = sqrt(-2.0 * log(U))* sin(2.0 * PI * V);
}
else
{
z= sqrt(-2.0 * log(U)) * cos(2.0 * PI * V);
}
phase = 1 - phase;
y=sqrt(fangcha)*z+qiwang;//映射到y服从均值为16,方差为70的正态随机数
return y;
}
void check_num(int i,int &a,int &check2)
{
int j;
for(j=1;j<=CLASS_NUM;j++)
{
if(rec[j]< aver_dian)
{
a=j;
check2=check;
return;
}
}
if(j==CLASS_NUM+1) aver_dian++;
}
int main()
{
//根据时间设定种子,不同时间运行会有不用的结果
srand(time(NULL));
memset(rec,0,sizeof(rec));
int check2=check;
printf("点名顺序:\n");
for(int i=1;i<=cishu;i++)
{
int a=(int)gaussrand();
if(a>=1&&a<=CLASS_NUM)
{
if(check2==1) check_num(i,a,check2);
else check2--;
rec[a]++;
printf("%d,", a);
}
else i--;
}
printf("\n\n统计:共点名%d次,每个人最少点到次数:%d \n",cishu,aver_dian-1);
for(int i=1;i<=CLASS_NUM;i++) printf("id:%d total:%d\n",i,rec[i]);
return 0;
}
注:学习委员需要每个人都要点到,那我就设定了一个每隔n次点名以后,直接按顺序选没点到的同学,保证每个人都能回答到,获得平时分,也就是此处的check_num函数。
参考资料:三种生成高斯分布随机数的方法