二倍均值法(公平版)

发出一个固定金额的红包,由若干个人来抢,需要满足哪些规则?

1.所有人抢到金额之和等于红包金额,不能超过,也不能少于。

2.每个人至少抢到一分钱。

3.要保证所有人抢到金额的几率相等。

假设剩余红包金额为M,剩余人数为N,那么有如下公式:

每次抢到的金额 = 随机区间 (0, M / N × 2)

这个公式,保证了每次随机金额的平均值是相等的,不会因为抢红包的先后顺序而造成不公平。举个例子:

假设有10个人,红包总额100元。100/10×2 = 20, 所以第一个人的随机范围是(0,20 ),平均可以抢到10元。

假设第一个人随机到10元,那么剩余金额是100-10 = 90 元。90/9×2 = 20, 所以第二个人的随机范围同样是(0,20 ),平均可以抢到10元。

假设第二个人随机到10元,那么剩余金额是90-10 = 80 元。80/8×2 = 20, 所以第三个人的随机范围同样是(0,20 ),平均可以抢到10元。

以此类推,每一次随机范围的均值是相等的。

  static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
var list = DivideRedPackage(100* 100, 10);
Console.WriteLine(string.Join(",", list));
int count = 0;
foreach (var item in list)
{
count += item;
}
Console.WriteLine(count);
}
System.Console.ReadKey();
} /// <summary>
/// 产生红包数组
/// </summary>
/// <param name="cashCount">红包总金额,单位分</param>
/// <param name="peopleNumber">红包人数</param>
/// <returns></returns>
static List<int> DivideRedPackage(int cashCount, int peopleNumber)
{
List<int> redPackageList = new List<int>();
if (cashCount <= peopleNumber)
{
for (int i = 0; i < cashCount; i++)
{
redPackageList.Add(1);
} return redPackageList;
} Random random = new Random(GetRandomSeed());
int restCash = cashCount, restPeople = peopleNumber;
for (int i = 0; i < peopleNumber - 1; i++)
{
var cash = random.Next(1, restCash / restPeople * 2);
restCash -= cash;
restPeople--;
redPackageList.Add(cash);
}
redPackageList.Add(restCash);
return redPackageList;
}

例如,产生的结果如下:

1960,189,234,1763,1211,1236,1340,53,1652,362
10000
1032,1380,456,1885,608,857,1541,452,1273,516
10000
976,955,749,936,1990,1177,781,325,527,1584
10000
794,935,272,216,2034,522,455,2313,2260,199
10000
1376,1539,1292,614,443,1874,889,544,821,608
10000
914,15,877,1738,604,932,321,983,3106,510
10000
659,791,800,1066,788,908,991,2473,495,1029
10000
1256,733,1385,667,1192,1237,455,105,2121,849
10000
1941,1173,567,1280,1558,618,183,644,133,1903
10000
1313,735,1198,1173,1288,522,1879,1155,59,678
10000

上述示例中需注意,Random是一个伪随机数生成器,在大多数 Windows 系统上,Random 类 (System) | Microsoft Docs 15 毫秒内创建的对象可能具有相同的种子值。因此,如果New Random如果在循环中使用,就必须提供随机的种子值。我们可以使用RNGCryptoServiceProvider 类 (System.Security.Cryptography) | Microsoft Docs类产生随机树种子。具体代码如下:

        /// <summary>
/// 产生加密的随机数种子值
/// </summary>
/// <returns></returns>
static int GetRandomSeed()
{
byte[] bytes = new byte[4];
System.Security.Cryptography.RNGCryptoServiceProvider rng =
new System.Security.Cryptography.RNGCryptoServiceProvider();
rng.GetBytes(bytes);
return BitConverter.ToInt32(bytes, 0);
}

线段切割法(手速版)

算法思路如下:

线段分割法就是把红包总金额想象成一条线段,而每个人抢到的金额,则是这条主线段所拆分出的子线段。

当N个人一起抢红包的时候,就需要确定N-1个切割点。

因此,当N个人一起抢总金额为M的红包时,我们需要做N-1次随机运算,以此确定N-1个切割点。

随机的范围区间是(1, M)。当所有切割点确定以后,子线段的长度也随之确定。这样每个人来抢红包的时候,只需要顺次领取与子线段长度等价的红包金额即可。

需要注意一下两点:

1.每个人至少抢到一分钱。

2.分割的线段如果重复需要重新切割

具体代码如下:

    class Program
{
static List<int> DivideRedPackage(int cashCount, int peopleNumber)
{
List<int> redPackageList = new List<int>();
if (cashCount <= peopleNumber)
{
for (int i = 0; i < cashCount; i++)
{
redPackageList.Add(1);
}
return redPackageList;
} Random random = new Random(GetRandomSeed());
int restPeople = peopleNumber;
List<int> lineList = new List<int>();
while (restPeople > 1)
{
var line = random.Next(1, cashCount);
if (lineList.Contains(line) == false)
{
lineList.Add(line);
restPeople--;
}
}
lineList.Sort(); redPackageList.Add(lineList[0]);
for (int i = 0; i < peopleNumber - 2; i++)
{
var cash = lineList[i + 1] - lineList[i];
redPackageList.Add(cash);
}
redPackageList.Add(cashCount - lineList[lineList.Count - 1]);
return redPackageList;
}
static int GetRandomSeed()
{
byte[] bytes = new byte[4];
System.Security.Cryptography.RNGCryptoServiceProvider rng =
new System.Security.Cryptography.RNGCryptoServiceProvider();
rng.GetBytes(bytes);
return BitConverter.ToInt32(bytes, 0);
}
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
var list = DivideRedPackage(100 * 100, 10);
Console.WriteLine(string.Join(",", list));
int count = 0;
foreach (var item in list)
{
count += item;
}
Console.WriteLine(count);
}
System.Console.ReadKey();
}
}

输出结果如下:

409,2233,1843,546,983,679,1621,460,369,857
10000
50,472,281,603,577,1007,3929,38,591,2452
10000
194,1241,675,209,3507,1714,1199,596,313,352
10000
2127,578,16,2413,1332,586,91,260,465,2132
10000
1015,1421,963,626,3031,955,171,1112,60,646
10000
118,352,1062,1128,8,374,1879,1707,1755,1617
10000
2805,592,391,90,1468,392,2201,40,1426,595
10000
145,251,2910,59,1065,235,2761,997,1564,13
10000
814,1725,1886,39,696,202,44,992,3099,503
10000
828,1281,2402,579,380,2246,154,855,564,711
10000

C#实现抢红包算法的更多相关文章

  1. java Random 抢红包算法

    红包有一个总金额和总数量,领的时候随机分配金额. 维护一个剩余总金额和总数量,分配时,如果数量等于1,直接返回总金额,如果大于1,则计算平均值,并设定随机最大值为平均值的两倍,然后取一个随机值,如果随 ...

  2. 抢红包算法 java

    抢红包的需求分析 抢红包的场景有点像秒杀,但是要比秒杀简单点.因为秒杀通常要和库存相关.而抢红包则可以允许有些红包没有被抢到,因为发红包的人不会有损失,没抢完的钱再退回给发红包的人即可.另外像小米这样 ...

  3. java 实现仿照微信抢红包算法,实测结果基本和微信吻合,附demo

    实现拼手气红包算法,有以下几个需要注意的地方: 抢红包的期望收益应与先后顺序无关 保证每个用户至少能抢到一个预设的最小金额,人民币红包设置的最小金额一般是0.01元,如果需要发其他货币类型的红包,比如 ...

  4. 面试题: java面试经历 已看1 抢红包如何分配每个人抢到的钱 有用 难点的面试题

    2018.03.09 深圳乐唯科技 我看了下感觉这公司貌似挺不错的,面试官人也挺好的,氛围应该很不错,可惜我实力不足,唉,接续努力,下面把面试中印象较深的三个问题写一下. 面试问题1:数据库删除重复数 ...

  5. Redis .NET操作

    Redis是一个支持数据结构更多的键值对数据库.它的值不仅可以是字符串等基本数据类型,也可以是类对象,更可以是Set.List.计数器等高级的数据结构. Memcached也可以保存类似于Set.Li ...

  6. Java编程的逻辑 (34) - 随机

    本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...

  7. 微信红包中使用的技术:AA收款+随机算法

    除夕夜你领到红包了吗?有的说“我领了好几K!”“我领了几W!” 土豪何其多,苦逼也不少!有的说“我出来工作了,没压岁钱了,还要发红包”.那您有去抢微信红包吗?微信群中抢“新年红包”春节爆红.618微信 ...

  8. 红包算法思考和总结 -- by jason.zhi

    参考: http://mp.weixin.qq.com/s?__biz=MzI2NjA3NTc4Ng==&mid=402360599&idx=1&sn=69318b235e0e ...

  9. java实现微信红包分配算法

    红包算法分析 有人认为,抢红包的额度是从0.01到剩余平均值*N(N是一个系数,决定最大的红包值)之间,比如一共发了10块钱,发了10个红包:第一个人可以拿到(0.01~1*N)之间的一个红包值,当然 ...

随机推荐

  1. Pycharm 使用备忘

    1.打开方法定义 快捷方式:[ctrl+左键]或者[Ctrl+B] 如果点击之后,打开不是[.py]文件,而是[.pyi]文件,可以把下面红框的参数删掉. 2.设置文件开头默认注释 # *_* cod ...

  2. 布客·ApacheCN 编程/后端/大数据/人工智能学习资源 2021.7

    公告 ApacheCN 翻译预计将于半年内恢复. 我们的开源项目必须有中文的 README,如果是文档类项目,必须全部中文,否则将会被清理.请大家贡献项目时一定要留意. 我们目标是[财务自由+情感自由 ...

  3. RSA公钥加密-私钥解密/私钥加密-公钥解密

    package com.tebon.ams.util;import org.apache.commons.codec.binary.Base64;import org.apache.log4j.Log ...

  4. String类为什么可以直接赋值

    在研究String直接赋值与new String的区别之前我们需要先了解java中的字符串常量池的概念 字符串常量池 String类是我们平常项目中使用频率非常高的一种对象类型,jvm为了提升性能和减 ...

  5. 动画移动scrollview内容

    #pragma mark - 按钮的点击 - (IBAction)top { /* [UIView animateWithDuration:2.0 animations:^{ // CGPoint o ...

  6. NoSQL 之 Redis配置与优化

    NoSQL 之 Redis配置与优化 1.关系数据库与非关系型数据库概述 2.关系数据库与非关系型数据库区别 3.非关系型数据库产生背景 4.Redis简介 5.Redis安装部署 6.Redis 命 ...

  7. Python--操作列表

    Python--操作列表 目录 Python--操作列表 一.遍历整个列表 1. 深入研究循环 2. 在for循环中执行更多操作 3. 在for循环结束后执行一些操作 二.避免缩进错误 1. 忘记缩进 ...

  8. Solution -「CF 917D」Stranger Trees

    \(\mathcal{Description}\)   Link.   给定一棵包含 \(n\) 个点的有标号树,求与这棵树重合恰好 \(0,1,\cdots,n-1\) 条边的树的个数,对 \(10 ...

  9. 【曹工杂谈】Mysql-Connector-Java时区问题的一点理解--写入数据库的时间总是晚13小时问题

    背景 去年写了一篇"[曹工杂谈]Mysql客户端上,时间为啥和本地差了整整13个小时,就离谱",结果最近还真就用上了. 不是我用上,是组内一位同事,他也是这样:有个服务往数据库in ...

  10. Devops 开发运维高级篇之Jenkins+Docker+SpringCloud微服务持续集成(上)

    Devops 开发运维高级篇之Jenkins+Docker+SpringCloud微服务持续集成(上) Jenkins+Docker+SpringCloud持续集成流程说明 大致流程说明: 1) 开发 ...