微信红包算法代码实现

微信红包算法思考学习研究

闲来无事,研究下微信的红包算法,也思考下可以实现的其他算法,略作记录。

微信红包的随机算法不是在发红包时就算好的,而是用户在领取红包时实时计算出客户领取红包金额,因此红包的算法重点在于如何公平地算出领取人领取的红包金额。
可以转换为问题:从资金为S、个数为N的红包池中公平地随机取一个随机数,要求保证每个人都可以领取到红包。

总额随机法

算法描述:假设当前红包池的金额为B=100,待领取人数为P=10,从中领取一个随机红包的算法时为保证每个客户有红包可领,先保留最小预留金额K=P=10,然后对其他部分直接取随机值R=rand()%(B-P)=rand()%90,最后取R+1作为本次随机领取的红包金额。
优缺点:算法不公平,未体现在平均值范围内波动,结果差异很大。
主要实现算法如下:

// 总额随机法
// 算法描述:假设当前红包池的金额为B=100,待领取人数为P=10,从中领取一个随机红包的算法时
// 为保证每个客户有红包可领,先保留最小预留金额K=P=10,然后对其他部分直接取随机值
// R=rand()%(B-P)=rand()%90,最后取R+1作为本次随机领取的红包金额
// 优缺点:算法不公平,未体现在平均值范围内波动,结果差异很大
static size_t PickRand(size_t nBal, size_t nNum)
{
    size_t nPick = nBal - nNum;
    if (nNum != 1)
    {
        nPick = (nPick ? rand() % nPick : 0) + 1;
    }
    else
    {
        nPick = nBal;
    }
    cout << "恭喜你,你领取的红包金额为:" << nPick / 100.0f << "元" << endl;
    return nPick;
}

平均数加减法

算法描述:假设当前红包池的金额为B=100,待领取人数为P=10,从中领取一个随机红包的算法时,先取的平均数AVG=B/P=10,然后在平均数上再进行随机的加或减平均数以内的随机数RAVG=rand()/AVG,如此即可从红包池中获取一个随机金额的红包R=AVG+RAVG或R=AVG-RAVG。
注意,红包池内的P=1时,直接取随机红包金额为红包池的金额B,即R=B
优缺点:算法公平,在均值范围内波动

// 平均数加减法
// 算法描述:假设当前红包池的金额为B=100,待领取人数为P=10,从中领取一个随机红包的算法时,
// 先取的平均数AVG=B/P=10,然后在平均数上再进行随机的加或减平均数以内的随机数RAVG=rand()/AVG,
// 如此即可从红包池中获取一个随机金额的红包R=AVG+RAVG或R=AVG-RAVG
// 注意,红包池内的P=1时,直接取随机红包金额为红包池的金额B,即R=B
// 优缺点:算法公平,在均值范围内波动
static size_t PickAvgPM(size_t nBal, size_t nNum)
{
    // 先预定最小金额
    size_t nPick = 1;

    if(nNum != 1)
    {
        // 总额减去基本数
        nBal -= nNum;

        // 随机金额:平均数±平均数内的随机值
        size_t nAvg = nBal / nNum;
        size_t nRand = (nAvg ? rand() % nAvg : 0);
        nAvg += (rand() % 2 ? nRand : 0 - nRand);
        nPick += nAvg;
    }
    else
    {
        nPick = nBal;
    }

    cout << "恭喜你,你领取的红包金额为:" << nPick / 100.0f << "元" << endl;
    return nPick;
}

微信红包法

算法描述:假设当前红包池的金额为B=100,待领取人数为P=10,从中领取一个随机红包的算法时,先取的平均数AVG=B/P=10,据此计算出本次领取金额的范围为2AVG,即1至20,如此即可从红包池中获取一个随机金额的红包R=rand()%(2AVG)=1 + rand()%20。
注意,红包池内的P=1时,直接取随机红包金额为红包池的金额B,即R=B
优缺点:算法公平,在均值范围内波动

// 微信红包法
// 算法描述:假设当前红包池的金额为B=100,待领取人数为P=10,从中领取一个随机红包的算法时,
// 先取的平均数AVG=B/P=10,据此计算出本次领取金额的范围为2*AVG,即1至20,
// 如此即可从红包池中获取一个随机金额的红包R=rand()%(2*AVG)=1 + rand()%20
// 注意,红包池内的P=1时,直接取随机红包金额为红包池的金额B,即R=B
// 优缺点:算法公平,在均值范围内波动
static size_t PickAvgWx(size_t nBal, size_t nNum)
{
    size_t nPick = nBal;
    if (nNum != 1)
    {
        // 平均数*2
        // 总金额-总人数是为了保证剩余的每个人都有红包可领
        size_t nAvg = (nBal-nNum)/nNum;
        nPick = (nAvg? rand()%nAvg:0) + 1;
    }
    cout << "恭喜你,你领取的红包金额为:" << nPick / 100.0f << "元" << endl;
    return nPick;
}

测试代码

#include 
#include 
#include 
#include 
#include 

using namespace std;
class CDivideRedPacket
{
private:
    size_t  _nNum;       // 红包个数
    size_t  _nBal;       // 红包总金额,单位为分。取整型而不是浮点型提高性能
    string  _strRemark;  // 备注信息

    size_t  _nPicked;    // 已经领取的个数
    size_t  _nPickedBal; // 已经领取的金额

public:
    // 构造析构函数
    CDivideRedPacket() { Reset(); }

    // 参数设置
    bool SetPara(size_t nNum, double fBal, string strRemark = "恭喜发财,大吉大利")
    {
        // 重置数据
        Reset();
        if (fBal <= 0)
        {
            cout << "红包金额必须大于0" << endl;
            return false;
        }
        _nBal = static_cast<size_t>(fBal * 100);
        if (nNum == 0)
        {
            cout << "红包个数必须大于0" << endl;
            return false;
        }
        if (nNum > _nBal)
        {
            cout << "红包个数太多,金额不够,请调整" << endl;
            return false;
        }
        _nNum = nNum;
        _strRemark = strRemark;
        cout << "设置红包成功:人数[" << _nNum << "] 金额[" << _nBal / 100.0f << "] 备注[" << _strRemark << "]" << endl;
        return true;
    }

    // 领取红包
    bool Pick()
    {
        // 检查红包数
        if (_nNum == 0 || _nNum == _nPicked)
        {
            assert(_nBal == _nPickedBal);
            cout << "你来晚了,红包已被领完"<< endl;
            return false;
        }
        // 分配红包
        _nPickedBal += PickAvgWx(_nBal - _nPickedBal, _nNum - _nPicked++);
    }

private:
    void Reset()
    {
        _nBal = 0;
        _nNum = 0;
        _strRemark = "恭喜发财,大吉大利";
        _nPicked = 0;
        _nPickedBal = 0;
    }

    // 随机法
    // 算法描述:假设当前红包池的金额为B=100,待领取人数为P=10,从中领取一个随机红包的算法时
    // 为保证每个客户有红包可领,先保留最小预留金额K=P=10,然后对其他部分直接取随机值
    // R=rand()%(B-P)=rand()%90,最后取R+1作为本次随机领取的红包金额
    // 优缺点:算法不公平,未体现在平均值范围内波动,结果差异很大
    static size_t PickRand(size_t nBal, size_t nNum)
    {
        size_t nPick = nBal - nNum;
        if (nNum != 1)
        {
            nPick = (nPick ? rand() % nPick : 0) + 1;
        }
        else
        {
            nPick = nBal;
        }
        cout << "恭喜你,你领取的红包金额为:" << nPick / 100.0f << "元" << endl;
        return nPick;
    }

    // 平均数加减法
    // 算法描述:假设当前红包池的金额为B=100,待领取人数为P=10,从中领取一个随机红包的算法时,
    // 先取的平均数AVG=B/P=10,然后在平均数上再进行随机的加或减平均数以内的随机数RAVG=rand()/AVG,
    // 如此即可从红包池中获取一个随机金额的红包R=AVG+RAVG或R=AVG-RAVG
    // 注意,红包池内的P=1时,直接取随机红包金额为红包池的金额B,即R=B
    // 优缺点:算法公平,在均值范围内波动
    static size_t PickAvgPM(size_t nBal, size_t nNum)
    {
        // 先预定最小金额
        size_t nPick = 1;

        if(nNum != 1)
        {
            // 总额减去基本数
            nBal -= nNum;

            // 随机金额:平均数±平均数内的随机值
            size_t nAvg = nBal / nNum;
            size_t nRand = (nAvg ? rand() % nAvg : 0);
            nAvg += (rand() % 2 ? nRand : 0 - nRand);
            nPick += nAvg;
        }
        else
        {
            nPick = nBal;
        }

        cout << "恭喜你,你领取的红包金额为:" << nPick / 100.0f << "元" << endl;
        return nPick;
    }

    // 微信红包法
    // 算法描述:假设当前红包池的金额为B=100,待领取人数为P=10,从中领取一个随机红包的算法时,
    // 先取的平均数AVG=B/P=10,据此计算出本次领取金额的范围为2*AVG,即1至20,
    // 如此即可从红包池中获取一个随机金额的红包R=rand()%(2*AVG)=1 + rand()%20
    // 注意,红包池内的P=1时,直接取随机红包金额为红包池的金额B,即R=B
    // 优缺点:算法公平,在均值范围内波动
    static size_t PickAvgWx(size_t nBal, size_t nNum)
    {
        size_t nPick = nBal;
        if (nNum != 1)
        {
            // 平均数*2
            // 总金额-总人数是为了保证剩余的每个人都有红包可领
            size_t nAvg = (nBal-nNum)/nNum;
            nPick = (nAvg? rand()%nAvg:0) + 1;
        }
        cout << "恭喜你,你领取的红包金额为:" << nPick / 100.0f << "元" << endl;
        return nPick;
    }
};

int main()
{
    CDivideRedPacket rp;
    srand(time(NULL));

    int nNum = 0;
    double fBal = 0.0f;
    while (true)
    {
        nNum = 1 + rand() % 20;
        fBal = (1 + rand() % 1000000) / 100.f;
        if (rp.SetPara(nNum, fBal))
        {
            while (rp.Pick());
        }

        cout << "-----END----" << endl;
        // 如果为了验证正确性,可注释掉该暂停时间,查看程序运行是否异常
        Sleep(3000);
    }
    return 0;
}

执行结果示例

设置红包成功:人数[4] 金额[175.05] 备注[恭喜发财,大吉大利]
恭喜你,你领取的红包金额为:36.35元
恭喜你,你领取的红包金额为:38.87元
恭喜你,你领取的红包金额为:26.84元
恭喜你,你领取的红包金额为:72.99元
你来晚了,红包已被领完
-----END----
设置红包成功:人数[6] 金额[237.17] 备注[恭喜发财,大吉大利]
恭喜你,你领取的红包金额为:10.41元
恭喜你,你领取的红包金额为:28.64元
恭喜你,你领取的红包金额为:41.67元
恭喜你,你领取的红包金额为:9.61元
恭喜你,你领取的红包金额为:55.63元
恭喜你,你领取的红包金额为:91.21元
你来晚了,红包已被领完
-----END----
设置红包成功:人数[8] 金额[264.63] 备注[恭喜发财,大吉大利]
恭喜你,你领取的红包金额为:9.36元
恭喜你,你领取的红包金额为:14.42元
恭喜你,你领取的红包金额为:14.78元
恭喜你,你领取的红包金额为:37.58元
恭喜你,你领取的红包金额为:4.21元
恭喜你,你领取的红包金额为:20.68元
恭喜你,你领取的红包金额为:54.21元
恭喜你,你领取的红包金额为:109.39元
你来晚了,红包已被领完
-----END----

你可能感兴趣的:(算法,杂文)