微信紅包算法思考學習研究
閒來無事,研究下微信的紅包算法,也思考下可以實現的其他算法,略作記錄。
微信紅包的隨機算法不是在發紅包時就算好的,而是用戶在領取紅包時實時計算出客戶領取紅包金額,因此紅包的算法重點在於如何公平地算出領取人領取的紅包金額。
可以轉換爲問題:從資金爲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----