微信紅包算法代碼實現

微信紅包算法思考學習研究

閒來無事,研究下微信的紅包算法,也思考下可以實現的其他算法,略作記錄。

微信紅包的隨機算法不是在發紅包時就算好的,而是用戶在領取紅包時實時計算出客戶領取紅包金額,因此紅包的算法重點在於如何公平地算出領取人領取的紅包金額。
可以轉換爲問題:從資金爲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 <iostream>
#include <string>
#include <assert.h>
#include <time.h>
#include <windows.h>

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----
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章