【随机算法1】——微信抢红包的随机算法

一、原题

编写自定义函数void hongbao(int  amount,int  renshu),实现红包分配算法,在函数中输出每个人分配到的红包金额。amount,表示红包总金额,renshu表示分配总人数。编写main函数测试,并且在main函数中,输入红包总金额和分配总人数。(不限制算法,尽可能合理)

二、算法及思路分析

初次看到这道题,笔者认为这只是一道简单的生成随机数的问题,考察的重点是rand函数,srand函数,同时确保随机数总和一定。但随着笔者的逐步编写,发现这是一个并不那么简单的问题,下面就跟着笔者一起来看看这个程序的编写思路。

1、rand函数,srand函数,time函数

首先我们介绍一下rand函数。rand 函数是 C 标准库中的一个函数,用于生成伪随机数,定义在 <stdlib.h> 头文件中。rand 函数会返回一个介于 0 和 RAND_MAX 之间的整数。RAND_MAX 是一个常量,表示 rand 函数可以返回的最大值。根据 C 标准,RAND_MAX 至少为 32767,但实际值取决于具体的实现。

之所以说rand函数生成的是伪随机数,因为如果只用rand函数,每一次运行程序的时候生成的随机数是一样的。rand函数生成的数字在统计上具有随机数的特性,但实际上是通过一个初始值(称为随机数种子)和一个固定的算法生成的。大多数c标准库里的rand 函数都基于线性同余法 (LCG)。

srand函数的作用是设置随机数种子。如果没有调用 srand,则默认随机数种子值为 1。所以只用一个rand函数生成的随机数,在每一次的运行中都是一样的。

因此,我们需要用srand函数来设置随机数,我们需要找一个不断地变化的数,来设置随机数种子。因此,我们想到了时间。每一次运行程序的的时间都是不一样的,因此,我们把时间设为随机数种子,就可以实现每一次都不同的随机数生成。

代码如下:

srand((unsigned int)time(NULL))

2、调整随机数范围与格式

rand函数生成的随机数是0~RAND_MAX(至少为32767),根据不同实际情况,我们要调整范围。本题的抢红包随机算法,需要生成0.01到输入的总金额之间的两位小数浮点型随机数。

尤其是总和一定,这说明每一次生成随机数的范围是在变化的。我们可以设计如下代码:

float max = amount2 - (renshu2 - 1) * min;//amount2代表实时剩余总金额,renshu2代表实时剩余人数
        float temp_random = (float)rand() / RAND_MAX;//生成临时随机数,强制转换实型,再除以最大值(生成了一个0~0.99999999.....之间的随机数)
        float random = min + (max - min) * temp_random;//0~0.9999...缩放至0.01~实时最大金额的范围
        random = round(random * 100) / 100.0;//保留两位小数,作为随机生成的红包

3、 达成题目的其他要求完成代码

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

void hongbao(int amount, int renshu);//声明函数原型

int main()//主函数部分
{
    int amount, reshu;
    srand((unsigned int)time(NULL));// 设置当前时间作为随机数生成器的种子
    scanf("%d %d", &amount, &reshu); // 输入总金额和人数
    hongbao(amount, reshu);// 调用红包分配函数
    return 0;
}

void hongbao(int amount, int renshu)
{
    int i;
    float min = 0.01f;  // 最小红包金额
    float all_amount = amount;  // 总金额
    float amount2 = all_amount;  // 当前剩余金额
    float renshu2 = renshu;  // 当前剩余人数

    for (i = 1; i < renshu; i++)// 循环分配红包给每个人(除了最后一个人)
    {
        float max = amount2 - (renshu2 - 1) * min;// 计算当前最大可能的红包金额(即随机数范围)
        float temp_random = (float)rand() / RAND_MAX; // 生成一个 [0, 1) 区间内的随机浮点数
        float random = min + (max - min) * temp_random;   // 将随机数缩放到 [min, max] 区间内
        random = round(random * 100) / 100.0;// 将随机数四舍五入到小数点后两位
        printf("%d %.2f\n", i, random);// 打印当前人的编号和分配的红包金额
        amount2 -= random;  // 更新剩余金额和剩余人数
        renshu2--;
    }
    printf("%d %.2f", renshu, amount2);// 此处考虑保留小数是否会造成所有红包加起来不等于总金额,所以分配最后一个红包时用减法,确保所有金额都被分配完
}

 4、调试与运行

然而笔者测试数据的时候却发现了问题:

 

 

随着红包个数的增加,笔者发现出现了大量的0.01元红包,几乎全部的金额都集中在个别几个红包中,而且还扎堆在前几个红包里。说明算法存在重大的问题。

 三、算法优化

经过仔细分析,笔者发现笔者的随机数算法生成的随机数存在明显的下降趋势。笔者认为这是动态的随机范围造成的,在笔者原来的代码里,每一个红包的随机数范围是在不断变化的,尤其是最大值在不断的减小,这就是造成这个下降趋势的随机数列的罪魁祸首。因此,我们需要优化我们的算法,在固定的范围内生成随机数

1、隔板法思想 

我们不妨换一个思路,假设我们要发100个红包,总金额1000元。以0.01为最小单位,在0~1000内随机生成99个两位小数的数字(即隔板),每一个红包的金额是相邻两块隔板间的距离。这样做的好处是可以不改变随机数生成的范围,也就不会造成这样趋势下降的随机数列。

生成的隔板数字我们需要存起来,因此,我们创建一个隔板数组来存放这些隔板数字。

代码实现:

for (i = 1; i < renshu; i++)
{
	float temp_random = (float)rand() / RAND_MAX;
	float random = min + (max - min) * temp_random;
	random = round(random * 1000) / 1000.0;
	arr[i - 1] = random;
}

2、隔板排序

需要注意,我们生成的隔板是随机的数据,为了后续方便处理这些数据,我们需要把这个隔板数组里的数,按从小到大的顺序排列一下。

我们可以采用经典的冒泡排序算法

代码如下:

void paixu(float arr[], int n)
{
    int i, j;
    float temp;
    for (i = 0; i < n - 1; i++) // 外层循环控制遍历次数
    {
        for (j = 0; j < n - 1 - i; j++) // 内层循环进行相邻元素的比较和交换
        {
            if (arr[j] > arr[j + 1])    // 如果当前元素大于下一个元素,则交换它们
            {
                temp = arr[j];          // 临时保存当前元素
                arr[j] = arr[j + 1];    // 将下一个元素赋值给当前元素
                arr[j + 1] = temp;      // 将临时保存的当前元素赋值给下一个元素
            }
        }
    }
}

该算法实现过程是从数组第一个元素开始,遍历每一个元素,为每一个元素找到它应该在的位置。

外层循环由数组元素数量决定,控制遍历次数与次序。内层循环不断比较相邻两个元素,一旦当前的元素大于下一个元素就交换。如此循环往复。

实际上就是内层循环每执行完一次回到外层循环时,最大的元素被移到了数组的末尾。

示例

假设我们有一个浮点数数组 arr = {3.2, 1.5, 4.8, 2.1, 5.0},我们将使用 paixu 函数对其进行排序。

arr = [3.2, 1.5, 4.8, 2.1, 5.0]
第一轮遍历

比较 3.2 和 1.5,交换:

arr = [1.5, 3.2, 4.8, 2.1, 5.0]

比较 3.2 和 4.8,不交换: 

arr = [1.5, 3.2, 4.8, 2.1, 5.0]

比较 4.8 和 2.1,交换: 

arr = [1.5, 3.2, 2.1, 4.8, 5.0]

 比较 4.8 和 5.0,不交换:

arr = [1.5, 3.2, 2.1, 4.8, 5.0]
第二轮遍历

比较 1.5 和 3.2,不交换

arr = [1.5, 3.2, 2.1, 4.8, 5.0]

比较 3.2 和 2.1,交换:

arr = [1.5, 2.1, 3.2, 4.8, 5.0]

比较 3.2 和 4.8,不交换:

arr = [1.5, 2.1, 3.2, 4.8, 5.0]
第三轮遍历

比较 1.5 和 2.1,不交换:

arr = [1.5, 2.1, 3.2, 4.8, 5.0]

比较 2.1 和 3.2,不交换:

arr = [1.5, 2.1, 3.2, 4.8, 5.0]
第四轮遍历

比较 1.5 和 2.1,不交换:

arr = [1.5, 2.1, 3.2, 4.8, 5.0]
完成排序 

 运用到本题中,可直接将上方的排序算法复制进程序里调用即可。

3、完善算法,调整输入输出等

四、项目完成

本题的最终代码如下:

#include <stdio.h>  // 标准输入输出库
#include <stdlib.h>  // 标准库,用于 rand 和 srand
#include <time.h>  // 时间库,用于 time
#include <math.h>  // 数学库,用于 round

void hongbao(int amount, int renshu);  // 声明红包分配函数
void paixu(float arr[], int n);  // 声明排序函数

int main()
{
    int amount, renshu;  // 定义总金额和人数变量
    srand((unsigned int)time(NULL));  // 使用当前时间作为随机数生成器的种子
    scanf("%d%d", &amount, &renshu);  // 输入总金额和人数
    hongbao(amount, renshu);  // 调用红包分配函数
    return 0;  // 主函数结束
}

void paixu(float arr[], int n)
{
    int i, j;  // 定义循环控制变量
    float temp;  // 定义临时变量用于交换
    for (i = 0; i < n - 1; i++)  // 外层循环控制遍历次数
    {
        for (j = 0; j < n - 1 - i; j++)  // 内层循环进行相邻元素的比较和交换
        {
            if (arr[j] > arr[j + 1])  // 如果当前元素大于下一个元素,则交换它们
            {
                temp = arr[j];  // 临时保存当前元素
                arr[j] = arr[j + 1];  // 将下一个元素赋值给当前元素
                arr[j + 1] = temp;  // 将临时保存的当前元素赋值给下一个元素
            }
        }
    }
}

void hongbao(int amount, int renshu)
{
    int i;  // 定义循环控制变量
    float min = 0.01f;  // 最小红包金额
    float max = amount;  // 最大红包金额
    float renshu2 = renshu;  // 当前剩余人数
    double sum = 0;  // 用于累计已分配的红包金额(用于算最后一人的红包金额)
    float arr[10000] = { 0 };  // 定义一个数组用于存储每个红包的金额
    int j;  // 定义循环控制变量
    for (i = 1; i < renshu; i++)  // 循环分配红包给每个人(除了最后一个人)
    {
        float temp_random = (float)rand() / RAND_MAX;  // 生成一个 [0, 1) 区间内的随机浮点数
        float random = min + (max - min) * temp_random;  // 将随机数缩放到 [min, max] 区间内
        random = round(random * 1000) / 1000.0;  // 将随机数四舍五入到小数点后三位
        arr[i - 1] = random;  // 将生成的随机数存入数组
    }

    paixu(arr, renshu - 1);  // 对生成的红包金额进行排序

    for (j = 0; j < renshu - 1; j++)  // 循环打印每个红包的金额
    {
        float amount2 = arr[j] - sum;  // 计算当前人的红包金额
        if (amount2 < min)  // 确保红包金额不低于最小值
            amount2 = min;
        printf("第%d人的红包为%.2f元\n", j + 1, amount2);  // 打印当前人的红包金额
        sum += amount2;  // 累计已分配的红包金额
    }
    float reat_amount = amount - sum;  // 计算最后一人的红包金额
    if (reat_amount < min)  // 确保最后一人的红包金额不低于最小值
        reat_amount = min;
    printf("第%d人的红包为%.2f元", renshu, amount - sum);  // 打印最后一人的红包金额
}

测试数据

至此,完整实现的微信红包随机算法。

 笔者只是一位初学者,如果任何大佬有更好的算法和思路,欢迎多多指导~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值