红包分配算法之截尾正态分布

来源:互联网 发布:白光led光谱数据 编辑:程序博客网 时间:2024/04/28 22:24

最近在研究红包生成算法,通过查阅资料和个人测试,现总结如下:
(1)红包数值是随机的,应该近似服从正态分布,即大部分红包钱数在平均值附近,小红包和大红包出现的概率都相对较低。
(2)每份红包数额是有上下限的,所以是个截尾正态分布。
(3)利用Box-Muller变换产生正态分布随机数。
事实上,我们可以通过反函数、中心极限定理、Box-Muller变换、Ziggurat算法等方法来获取正态分布随机数,但综合考虑,Box-Muller变换的处理效率和逻辑复杂都是最符合用来做红包分配算法的,下面就来看看吧。

Box-Muller 变换
(https://en.wikipedia.org/wiki/Box-Muller_transform)
基本思想是:要得到服从正态分布的随机数,先得到服从均匀分布的随机数再将服从均匀分布的随机数转变为服从正态分布。
如果在 (0,1] 值域内有两个独立的随机数字 U1 和 U2,可以使用以下两个等式中的任一个算出一个正态分布的随机数字 Z:
Z = R * cos( θ )

Z = R * sin( θ )
其中
R = sqrt(-2 * ln(U2))
θ = 2 * π * U1
正态值 Z 是服从标准正态分布的,即期望为 0 ,标准差为1 ,通过X = m + (Z * sd)可以将 Z 映射到一个平均值为 m、标准偏差为 sd 的统计量 X。

以上就是Box-Muller在百度百科的中文简介,证明方法可以查看相关资料。我们这里只要使用它的结论,并实现即可。

C++实现如下,仅供参考,大神略过。

#include <math.h>#include <limits>#include <vector>#define TOW_PI 2.0*3.14159265358979323846double GaussNoise(double mu, double sigma){    const double epsilon = std::numeric_limits<double>::min();    static double rand0, rand1;    static bool generate = false;    if (generate)    {        generate = false;        return rand1 * sigma + mu;    }    double u1, u2;    do    {        u1 = rand() * (1.0 / RAND_MAX);        u2 = rand() * (1.0 / RAND_MAX);    } while (u1 <= epsilon);    rand0 = sqrt(-2.0 * log(u1)) * cos(TOW_PI * u2);    rand1 = sqrt(-2.0 * log(u1)) * sin(TOW_PI * u2);    generate = true;    return rand0 * sigma + mu;}vector<double> Generate(double money, int num){    double min = 0.01;    vector<double> value_vec;    double mu, sigma, nose_value;    //为每个红包预留保底数额    double money_left = money - num * min;    for (int i = 0; i < num; ++i)    {        mu = money_left / (num - i);        sigma = mu / 2;        nose_value = GaussNoise(mu, sigma);        //截尾处理        nose_value = nose_value < 0 ? 0 : nose_value;        nose_value = nose_value > money_left ? money_left : nose_value;        money_left -= nose_value;        value_vec.push_back(nose_value + min);    }    //剩余钱数处理    while (money_left > 0)    {        for (int i = 0; i < value_vec.size(); ++i)        {            if (money_left == 0)            {                break;            }            value_vec[i]++;            money_left--;        }    }    return value_vec;}

函数GaussNoise(double mu, double sigma)是Box-Muller的实现,函数Generate(double money, int num)是产生红包序列的接口,这里需要注意的几点:
(1)均值mu是剩余钱数的均值
(2)由正态分布三个sigma定理可知, 生成的随机数值有95%概率落在(mu-2 * sigma, mu+2 * sigma)内,为了使得mu-2*sigma = 0,sigma = mu/2即可。
(3)对于生成的随机数落在[0, money_left]以外区间的情况,采用截断处理,即所谓的截尾正态分布。
(4)剩余钱数处理,这里处理比较简单,按序给每个红包依次加1,直到剩余钱数为0,当然了还有更好的方法,这里就不详细介绍了。
通过以上算法,得到结果很友好,基本满足了红包随机分配的要求。几组测试数据如下:
钱数为100,份数为10结果:1.01 4.21714 17.0492 2.79062 7.40379 16.4524 23.4671 7.46014 14.9811 10.7486
钱数为200,份数为10结果:31.8167 5.15938 11.9885 20.8192 38.3667 19.7017 5.04915 40.7632 19.6593 6.67611
钱数为500,份数为10结果:79.5507 12.8873 29.9652 52.0486 95.9306 49.2541 12.6116 101.923 49.1481 16.6803

以上就是本人的总结,有参考别人结论的,有自己个人观点的,做个日记,方便以后学习。

0 0