还记着longlong ago

我还在做绿色征途手游版的时候

有天

策划同学要求同事

一定要优化下抢红包算法

本着划水第一

吃瓜并列第一的原则

于是

我听到了一堆数学名词

***定理

XXX公式

呼~

是我不配了

怪我没有好好学习

再仔细听一听

问题原来是

原先的红包分配算法是

先抢的人从总金额随机

后抢的人从剩余金额中随机

所以导致抢红包越早

抢的金额就越大

策划现在就想让金额稍微平均下

嚯,我直呼好家伙

就这个东西

有必要整那些有的没的吗

一会定理

一会公式的

最后他们的方案

也很务实

就是每个人先做个保底

再根据之前的红包算法进行分配

果然大隐隐于市

翠花,上酸菜

分析:

针对这个问题的解决方法,有四种(普通法,线段切割法,双倍随机法,投篮球法)。

前三种算法,网上基本都在流传,投篮球算法,是我自己瞎起的名字。

这个算法还是因为当年校招,面试北京涂鸦移动,面试官现场引导我一个问题,用了投篮球这个例子。而我写这篇博客的时候,想起那个算法,蛮适用的,因而叫他投篮球算法。

一、普通法:每次针对剩余总金额做一次随机,随机值就是第一个人的数值。这个算法也就是这个同事之前写的要求被优化的算法。这个算法的好处是完全随机,坏处是极有可能造成前人抢的太多,后人太少。例如,100元分给10个人,第一个人在0-100随机,均值为50。而第二个人的均值只剩25,之后是12.5。

而他们修改后的方案,大致就是先有保底。然后再随机,大致算法如下:

//普通法    有保底
//iTotalGold总金额    iNum份数    iBaseGold保底金额
void oldThink(int iTotalGold, int iNum, int iBaseGold)
{if (iBaseGold*iNum > iTotalGold){cout << "保底太多 " << iBaseGold<<endl;return;}iTotalGold -= (iBaseGold *iNum);std::vector<int> veGold(iNum);for (int i = 0; i < iNum-1; ++i){int iAddNum = 0;if(iTotalGold != 0)iAddNum += rand() % (iTotalGold);veGold[i] = iAddNum + iBaseGold;iTotalGold -= iAddNum;}veGold[iNum - 1] = iTotalGold + iBaseGold;cout << "普通法:" << endl;copy(veGold.begin(), veGold.end(), ostream_iterator<int>(cout, " "));cout << endl;
}
​
int main()
{int iTotalGold = 100;int iNum = 10; int iBaseGold = 5;oldThink(iTotalGold, iNum, iBaseGold);return 0;
}

二、线段切割法:将总金额想象成一条那么长的线段,需要分割成num份,随机num-1次,将每次的随机值映射到该线段上。这样的好处是将随机交给程序,缺点是有小概率造成某个人分配过多。例如,100个人分10个红包,我们除了需要考虑随机值重合之外,每次完全随机,可能造成不够随机的情况。

//线段切割法    无保底
//iTotalGold总金额    iNum份数    iBaseGold保底金额
void cutline(int iTotalGold, int iNum)
{if (iNum > iTotalGold){return;}if (iNum == iTotalGold){for (int i = 0; i < iNum; ++i)cout << 1 << " ";cout << endl;return;}std::set<int> setGold;for (int i = 0; i < iNum-1; ++i){while (1){int iPos = rand() % iTotalGold;if (setGold.find(iPos) == setGold.end()){setGold.insert(iPos);break;}}}cout << "线段切割法(无保底):" << endl;int iPreLine = 0;for (auto &it : setGold){cout << it - iPreLine << " ";iPreLine = it;}cout << iTotalGold - iPreLine << " ";cout << endl;
}
​
int main()
{int iTotalGold = 100;int iNum = 10; int iBaseGold = 5;cutline(iTotalGold, iNum);return 0;
}

这种算法,相比于第一种已经非常好了。如果觉得过于随机,可以针对这种算法做保底策略。可以预见,效果也一定好于第一种。

三、双倍随机法:每次随机的时候,取0-每个人平均金额的2倍进行随机。这样已经基本完成了我们想要的样子,不错的方法。例如:100个人分10分,每次随机都是0-100/10*2去随机,基本可以保证每个人平均在10左右,是相当平均的算法。不过需要注意最后几个人可能已经不足20的情况。

//双倍随机法
//iTotalGold总金额    iNum份数    iBaseGold保底金额
void twobase(int iTotalGold, int iNum, int iBaseGold)
{if (iNum *iBaseGold > iTotalGold){cout << "保底太多 " << iBaseGold << endl;return;}std::vector<int> veGold(iNum, iBaseGold);iTotalGold -= iNum*iBaseGold;int iBaseTmp = iTotalGold / iNum * 2;for (int i = 0; i < iNum - 1; ++i){if (iTotalGold == 0)break;int iTmp = 0;if (iTotalGold >= iBaseTmp)iTmp = rand() % iBaseTmp;elseiTmp = rand() % iTotalGold;veGold[i] += iTmp;iTotalGold -= iTmp;}veGold[iNum - 1] = iTotalGold;cout << "双倍随机法:" << endl;copy(veGold.begin(), veGold.end(), ostream_iterator<int>(cout, " "));cout << endl;
}
​
int main()
{int iTotalGold = 100;int iNum = 10; int iBaseGold = 5;twobase(iTotalGold, iNum, iBaseGold);return 0;
}
​

从效果可以看出,这种随机方法,已经达到了非常棒的随机效果。几乎可以避免玩家的投诉了。

四、我自创的名字,投篮球法:之前总是用金额去除以人数num以寻求平均。而投篮球法,则是以金额的单元值最为基准。以上三种算法都在极力的寻求随机,而投篮球法则是为了保证完全平均。例如:100个人分10分,每次取金额的最小单元值,比如一块钱,然后把10个人当成篮筐,一块钱当成篮球,每次都去投篮。这样的好处是不用模仿完全随机,他本身就是完全随机,坏处也很明显,循环次数过多。

针对于他循环过多的缺点,我自己做了一层优化。采用最小金额的单元值,例如每次取三块、五块。这种优化,一来可以避免循环过多。二来也可以避免极端情况下过于随机的结果。

//投篮球法    有保底
//iTotalGold总金额    iNum份数    iBaseGold保底金额
void basketball(int iTotalGold, int iNum, int iBaseGold)
{if (iBaseGold*iNum > iTotalGold){cout << "保底太多 " << iBaseGold << endl;return;}iTotalGold -= (iBaseGold *iNum);std::vector<int> veGold(iNum, iBaseGold);for (int i = 0; i < iTotalGold; i += 2) //这里基准值用了2,减少循环次数{int iPos = rand() % iNum;veGold[iPos] += 2;}cout << "投篮球法:" << endl;copy(veGold.begin(), veGold.end(), ostream_iterator<int>(cout, " "));cout << endl;
}
​
int main()
{int iTotalGold = 100;int iNum = 10; int iBaseGold = 5;basketball(iTotalGold, iNum, iBaseGold); return 0;
}

闲杂人等回避

我要装b了

从数学理论来说,只要随机次数足够多,那么结果一定是无限趋近于平衡的。所以这种算法,虽然循环次数过多,但是数据量够大的情况下,他一定是最优、最平衡的。

当然,算法服务于功能,上述结论局限于,策划希望大家拿到的都差不多。

最后,四种算法一起运行下,来对比下结果。

上图还存在一个小点,两次运行程序,得到的结果完全相同。这是因为rand函数在C++老版本里的随机因子是固定值的问题。关于随机因子和随机数,改天有空了再整理一篇文章专门讲述下。

这里,先解决下这种情况。只需要每次在程序运行时,重新给定随机因子就可以了。例如:srand((unsigned)time(0));

运行结果:

源码:

#include <iostream>
#include <vector>
#include <stdlib.h>
#include <iterator>
#include <set>
#include <time.h>
using namespace std;
​
​
​
//普通法    有保底
//iTotalGold总金额    iNum份数    iBaseGold保底金额
void oldThink(int iTotalGold, int iNum, int iBaseGold)
{if (iBaseGold*iNum > iTotalGold){cout << "保底太多 " << iBaseGold<<endl;return;}iTotalGold -= (iBaseGold *iNum);std::vector<int> veGold(iNum);for (int i = 0; i < iNum-1; ++i){int iAddNum = 0;if(iTotalGold != 0)iAddNum += rand() % (iTotalGold);veGold[i] = iAddNum + iBaseGold;iTotalGold -= iAddNum;}veGold[iNum - 1] = iTotalGold + iBaseGold;cout << "普通法:" << endl;copy(veGold.begin(), veGold.end(), ostream_iterator<int>(cout, " "));cout << endl;
}
​
​
//线段切割法    无保底
//iTotalGold总金额    iNum份数    iBaseGold保底金额
void cutline(int iTotalGold, int iNum)
{if (iNum > iTotalGold){return;}if (iNum == iTotalGold){for (int i = 0; i < iNum; ++i)cout << 1 << " ";cout << endl;return;}std::set<int> setGold;for (int i = 0; i < iNum-1; ++i){while (1){int iPos = rand() % iTotalGold;if (setGold.find(iPos) == setGold.end()){setGold.insert(iPos);break;}}}cout << "线段切割法(无保底):" << endl;int iPreLine = 0;for (std::set<int>::iterator it = setGold.begin(); it != setGold.end(); ++it){cout << *it - iPreLine << " ";iPreLine = *it;}cout << iTotalGold - iPreLine << " ";cout << endl;
}
​
​
//双倍随机法
//iTotalGold总金额    iNum份数    iBaseGold保底金额
void twobase(int iTotalGold, int iNum, int iBaseGold)
{if (iNum *iBaseGold > iTotalGold){cout << "保底太多 " << iBaseGold << endl;return;}std::vector<int> veGold(iNum, iBaseGold);iTotalGold -= iNum*iBaseGold;int iBaseTmp = iTotalGold / iNum * 2;for (int i = 0; i < iNum - 1; ++i){if (iTotalGold == 0)break;int iTmp = 0;if (iTotalGold >= iBaseTmp)iTmp = rand() % iBaseTmp;elseiTmp = rand() % iTotalGold;veGold[i] += iTmp;iTotalGold -= iTmp;}veGold[iNum - 1] = iTotalGold;cout << "双倍随机法:" << endl;copy(veGold.begin(), veGold.end(), ostream_iterator<int>(cout, " "));cout << endl;
}
​
​
//投篮球法    有保底
//iTotalGold总金额    iNum份数    iBaseGold保底金额
void basketball(int iTotalGold, int iNum, int iBaseGold)
{if (iBaseGold*iNum > iTotalGold){cout << "保底太多 " << iBaseGold << endl;return;}iTotalGold -= (iBaseGold *iNum);std::vector<int> veGold(iNum, iBaseGold);for (int i = 0; i < iTotalGold; i += 2) //这里基准值用了2,减少循环次数{int iPos = rand() % iNum;veGold[iPos] += 2;}cout << "投篮球法:" << endl;copy(veGold.begin(), veGold.end(), ostream_iterator<int>(cout, " "));cout << endl;
}
​
int main()
{srand((unsigned)time(0));int iTotalGold = 100;int iNum = 10;int iBaseGold = 5;oldThink(iTotalGold, iNum, iBaseGold);cutline(iTotalGold, iNum);twobase(iTotalGold, iNum, iBaseGold);basketball(iTotalGold, iNum, iBaseGold);return 0;
}

关注公众号【头发头发等等我】,查看更多分享

抢红包算法--四种抢红包算法对比(附源码)相关推荐

  1. 抢红包算法--四种抢红包算法对比

    线上测试服务器中,有个同事做的抢红包算法被要求优化,大概听了下他们的讨论,最后的结果竟然要用什么概率论等等一系列我听过的.没听过的名词去解决.我表示一脸懵*.其实解决的问题就是一个:抢红包算法不够平均 ...

  2. K近邻算法讲解与python实现(附源码demo下载链接)

    k近邻算法概述 对应demo源码及数据:传送门 K近邻(k-Nearest Neighbor,简称kNN)算法,是一种应用很广泛的监督学习算法.它非常有效且易于掌握,其工作机制也很简单:给定测试样本, ...

  3. 基于PSO算法的电网无功优化(附源码)

    目录 1.粒子群算法 2.电力系统无功优化 3.matpower工具箱 3.基于粒子群算法的电网无功优化 3.1 ieee14节点 3.1.1 变量及编码方式 3.1.2 目标函数 3.2 ieee3 ...

  4. 微信、QQ自动抢红包外挂(绿色、无广告--附源码)

    为什么80%的码农都做不了架构师?>>>    说明:该外挂仅供学习.娱乐,请勿用于其他用途,否则后果自负. 功能: 1.支持微信.QQ自动抢红包 2.支持手机待机下自动抢红包 3. ...

  5. leaflet-webpack 入门开发系列三地图分屏对比(附源码下载)

    前言 leaflet-webpack 入门开发系列环境知识点了解: node 安装包下载 webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载地址 ...

  6. Java的三种代理模式【附源码分析】

    Java的三种代理模式&完整源码分析 代理模式分为两种,静态代理和动态代理,动态代理包括JDK动态代理和Cglib动态代理. 静态代理 静态代理在使用时,需要定义接口或者父类,被代理对象与代理 ...

  7. jiegputo matlab转置,matlab实现用免疫克隆算法求二元函数的最优值(附源码)

    用免疫克隆算法求二元函数的最优值 求下面函数的最小值(最优解) ​ Goldstein-Price函数: ​ f1(x)=[1+(x1+x2+1)2(19-14x1+3x12-14x2+6x1x2+3 ...

  8. android多个列表显示,Android采用ListView三种显示数据列表(附源码)

    Android系统中列表形式的显示方式应该是我们最熟悉不过的界面了,例如通讯录.通话记录.信息列表等等,例如下面的形式: 我们在开发项目需要用到这种形式显示信息时除了调用系统给我们提供的ListVie ...

  9. MFC Windows 程序设计[九十四]之精美的时钟(附源码)

    MFC Windows 程序设计[九十四]之精美的时钟 程序之美 前言 主体 运行效果 核心代码 逻辑分析 结束语 程序之美 前言 MFC是微软公司提供的一个类库(class libraries),以 ...

最新文章

  1. C2679 二进制 没有找到接受 std::string 类型的右操作数的运算符(或没有可接受的转换)
  2. 关于java类型数据组的调用
  3. 正则表达式匹配两个特殊字符中间的内容
  4. 怎么判断日出时间早晚_云南8日游攻略以及费用,从西安到云南旅游怎么安排行程?...
  5. fillna函数_听说这些pandas函数,是数据科学家和软件工程师的最爱
  6. Windows Phone性能优化建议
  7. Linux进阶之路——常用命令总结
  8. 撸完这些JVM知识点,明天就去面试阿里P6+
  9. (八大方法、逐层深入,有你一定没见过的)使用INSERT语句向表中插入数据
  10. Python使用颜色块覆盖视频中指定区域的内容
  11. 解刨一台计算机,解剖一台计算机.doc
  12. 【转】C#的内存管理:堆栈、托管堆与指针
  13. php 和new date,将JavaScript new Date()转换为php DateTime()
  14. python开发简单计算器下载_python简单计算器
  15. 关于精益创业理念随想
  16. 苹果电脑系统如何设置成中文
  17. html实现边下边播mp4,MP4Info: 不用流媒体也可以简单实现MP4等视频的边下边播功能。...
  18. 解决The number of method references in a .dex file cannot exceed 64K的问题
  19. android移动日记代码,Android开源项目-小熊日记源码
  20. Azure DevOps On-Premises如何让你的代理联机

热门文章

  1. 匿名虚拟服务器,如何使 SMTP 虚拟服务器能够接受匿名发送的邮件扩展属性
  2. 账单分期和最低还款之间的差距你绝对想不到,以广发卡为例子,看看自动分期的好处。
  3. (Python)从零开始,简单快速学机器仿人视觉Opencv---运用四:图像损痕修复
  4. 【RISC-V】SiFive Unmatched开发板开发手记
  5. cc共享许可协议_如何以及为何使用知识共享许可的作品
  6. 10个超赞的jQuery图片滑块动画
  7. 九连环问题(Java)
  8. iOS H5原生WKWebView调起支付宝客户端支付方案
  9. 深度学习三巨头之Yann LeCun(杨立昆)简介
  10. 《Python程序设计入门与实践》219道课后习题答案