闲来无事,研究下微信的红包算法,也思考下可以实现的其他算法,略作记录。
微信红包的随机算法不是在发红包时就算好的,而是用户在领取红包时实时计算出客户领取红包金额,因此红包的算法重点在于如何公平地算出领取人领取的红包金额。
可以转换为问题:从资金为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----