第一次实验报告
程序语言: C
姓名: unicorn
学号: 12345678910
日期:2023/3/10
一、 问题重述
微信红包程序:给定一个钱数m,发红包人数n,其中10<=m, n<=200将钱数拆成几个指定的吉利数(如1.66,1.68, 16.8,1.78,17.8,1.88,18.8,1.99,5.20,0.66, 6.6, 6.66, 0.08, 0.88, 8.8, 8.88,0.99, 9.9, 9.99)并发出,要求要发出n个红包,分布比较均匀,并尽可能把钱发完。
二、 问题分析
首先观察到钱数和红包量都不大,所以不需要考虑算法的复杂度问题;接着就是如何定义吉利数,题目给出了19个吉利数,根据规律我们还想出了13.14,16.66,19.9,1.98,19.8 这5个吉利数
然后我们注意到“分布比较均匀“,为了使得分布比较均匀,我们考虑先计算红包金额的平均数,再根据各吉利数与红包金额的差距,确定该吉利数被选中的概率,通过随机数来进行红包的分配。我们先通过一层条件判断是否剩下的金额已经不够平均分配,不够的话就选取数值较小的随机数,如果够就进入第一层随机,第一层随机80%概率会在主随机吉利数数组中选取红包金额,主随机数数组由与平均数相差最近的几个数组成,20%的数由次随机吉利数数组组成并进入第二次随机,按照这些吉利数距离平均数的权重来获得他们被选中的概率,并且确保他们不会超过剩下金额,最后一个红包直接输出最后的剩下金额
三、 伪代码
伪代码如下:
void send(float money, int count)
float aver <- money / count; //平均数
srand((unsigned)time(NULL)); // 用系统时间初始化随机数生成器
/* 生成平均数距离 */
for i<-0 to TOTAL do
lucky[i].distance <- fabs( lucky[i].num - aver) ;
//对吉利数按吉利数本身进行排序
for i<-0 to TOTAL do
for j<-i to TOTAL do
if lucky[i].num>lucky[j].num then
t<-lucky[i];
lucky[i]<-lucky[j];
lucky[j]<-t;
//计算权重值和累计权重值
max<-lucky[TOTAL-1].num;
sum=max * TOTAL - sum;
for i <- Main to TOTAL do
weight[i] <- (max - lucky[i].distance) / sum *1000.0 ;
if i > 0 then
addwei[i] <- weight[i] + addwei[i-1];
addwei[TOTAL]<-1000;
//对吉利数按吉利数与平均数距离进行排序
for i<-0 to TOTAL do
for j<-i to TOTAL do
if lucky[i].distance>lucky[j].distance then
t<-lucky[i];
lucky[i]<-lucky[j];
lucky[j]<-t;
Main=0;
//计算主随机函数数量(与平均数距离小于2)和小于1的吉利数数组
for i<-0 to TOTAL do
if lucky[i].distance<2 then
Main++;
if lucky[i].num<1 then
small[point++]<-lucky[i].num;
/* 循环发n-1个红包 */
for i <- 1 to count do
packet_random<-0; //辅助计算随机数
if remain<((count-i+1)*aver) then
//先判断是否剩余金额不够,不够的话先使用比较小的数补充
if rand()%2 then
packet_money <- 0.01;
else packet_money <- small[rand()%point] ;
}
else {
/* 随机生成一个指定吉利数的红包金额:
分为两层随机,第一层判断金额数是落入主金额数组还是次金额数组
80%落入主金额,20%落入次金额 */
if rand()%5!=0 then
packet_money <- lucky[rand()%Main].num;
else
ok<-1;
while ok do
middle <- rand();
packet_random <- middle % 1000;
/* 在累加权重中查看随机数落点并生成对应金额红包 */
for j = Main to TOTAL-1 do
if packet_random < addwei[j+1]
&& packet_random >= addwei[j] then
packet_money <- luck[j];
if packet_money<remain then
ok<-0;
printf("第%d个红包:%.2f元\n",
i, packet_money,remain,(count-i+1)*aver);
/* 红包金额从总金额中扣除 */
remain -= packet_money;
//最后一个红包直接把剩余的钱发出去
printf("第%d个红包:%.2f元\n", count, remain);
四、代码
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#define TOTAL 24
#define MAIN 3
#define MIN 3
int Main=0;
struct Luckynum {
float num;
float distance;
} lucky[TOTAL];
float luck[TOTAL] = {1.66,1.68,16.8,1.78,17.8,1.88,18.8,1.99,
5.20,0.66,6.6,6.66,0.08,0.88,
8.8,8.88,0.99,9.9,9.99,13.14,16.66,19.9,1.98,19.8};
// 吉利数的列表
float small[TOTAL] = {0};
//吉利数中比较小的数的列表
int point=0; //small数组指针
void print(float aa[])//测试使用函数
{
for (int i = 0; i < TOTAL; i++) {
printf("%f\t",aa[i]);
}
}
void send(float money, int count)
{
float remain = money;
float packet_money;
float aver = money / count; //平均数
float dis[TOTAL]= {0}; //与平均数距离 ;即将吉利数放在平均数一侧
float weight[TOTAL]= {0}; //权重:不科学的权重计算方法以及可能总和不为100%
float addwei[TOTAL+1]= {0}; //累计权重
srand((unsigned)time(NULL)); // 用系统时间初始化随机数生成器
/* 生成平均数距离 */
for (int i = 0; i < TOTAL; i++) {
lucky[i].distance = fabs( lucky[i].num - aver) ;
}
//对吉利数按吉利数本身进行排序,采用冒泡排序,若数量较大则应该换更高效的排序方法
for(int i = 0 ; i < TOTAL ; i++) {
for(int j = i ; j < TOTAL ; j++) {
if(lucky[i].num>lucky[j].num) {
struct Luckynum t;
t=lucky[i];
lucky[i]=lucky[j];
lucky[j]=t;
}
}
}
//计算权重值和累计权重值
float sum=0,max=lucky[TOTAL-1].num;
sum=max * TOTAL - sum;
for (int i = Main; i < TOTAL; i++) {
weight[i] = (max - lucky[i].distance) / sum *1000.0 ;
if(i > 0) addwei[i] = weight[i] + addwei[i-1];
//printf("%f\t",weight[i]);
}
addwei[TOTAL]=1000;
//对吉利数按吉利数与平均数距离进行排序,采用冒泡排序,若数量较大则应该换更高效的排序方法
for(int i = 0 ; i < TOTAL ; i++) {
for(int j = i ; j < TOTAL ; j++) {
if(lucky[i].distance>lucky[j].distance) {
struct Luckynum t;
t=lucky[i];
lucky[i]=lucky[j];
lucky[j]=t;
}
}
}
Main=0;
for(int i = 0 ; i < TOTAL ; i++) {
if(lucky[i].distance<2)
Main++;
if(lucky[i].num<1)
small[point++]=lucky[i].num;
}
/* 循环发n-1个红包 */
for (int i = 1; i < count; i++) {
int packet_random=0; //辅助计算随机数
if(remain<((count-i+1)*aver)) { //先判断是否剩余金额不够,不够的话先使用比较小的数补充
packet_random =999999;
if(rand()%2)packet_money = 0.01;
else packet_money = small[rand()%point] ;
}
else {
/* 随机生成一个指定吉利数的红包金额:
分为两层随机,第一层判断金额数是落入主金额数组还是次金额数组
80%落入主金额,20%落入次金额 */
if(rand()%5!=0) {
packet_money = lucky[rand()%Main].num;
} else {
int ok=1;
while(ok) {
int middle = rand();
packet_random = middle % 1000;
/* 在累加权重中查看随机数落点并生成对应金额红包 */
for(int j = Main ; j < TOTAL-1 ; j ++) {
if(packet_random < addwei[j+1]
&& packet_random >= addwei[j]) {
packet_money = luck[j];
if(packet_money<remain)ok=0;
}
}
}
}
}
printf("第%d个红包:%.2f元\n", i, packet_money,remain,(count-i+1)*aver);
/* 红包金额从总金额中扣除 */
remain -= packet_money;
}
//最后一个红包直接把剩余的钱发出去
printf("第%d个红包:%.2f元\n", count, remain);
}
int main()
{
float money;
int count;
//先对吉利数结构体数组进行初始化
for(int i = 0 ; i < TOTAL ; i++) {
lucky[i].num=luck[i];
}
printf("请输入红包金额(10元至200元):");
scanf("%f", &money);
printf("请输入红包数量(10个至200个):");
scanf("%d", &count);
//for(int i = 0 ; i < TOTAL ; i++)
//printf("%.2f\n",luck[i]);
if ( (money < 10 || money >200)
|| (count < 10 || count >200)) {
printf("红包金额或数量不不合法\n");
return 0;
}
send(money, count);
return 0;
}
五、 实验结果
下列分别列出了总金额为200元,红包个数分别为20,50,80个时的实验结果,结果较均匀,较符合实际。
六、 总结
该方案存在两点主要问题:第一是吉利数不够完善,由于吉利数定义不全,导致吉利数列举不全,无法很好的展示。第二是分布概率算法不完善,使用平均数附近的正态分布进行计算可能会更科学。这里只采用两层随机,第二层随机选择与平均数的距离来设置权重,可能导致最后一个红包的金额过大。