C# 实现抢红包算法

简介: C# 实现抢红包算法

二倍均值法(公平版)


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

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
目录
相关文章
|
1月前
|
开发框架 算法 搜索推荐
C# .NET面试系列九:常见的算法
#### 1. 求质数 ```c# // 判断一个数是否为质数的方法 public static bool IsPrime(int number) { if (number < 2) { return false; } for (int i = 2; i <= Math.Sqrt(number); i++) { if (number % i == 0) { return false; } } return true; } class Progr
81 1
|
1月前
|
搜索推荐 算法 C#
【Unity 3D】C#中冒泡排序、选择排序、插入排序等算法的详解(附源码 超详细)
【Unity 3D】C#中冒泡排序、选择排序、插入排序等算法的详解(附源码 超详细)
69 1
|
17天前
|
存储 编解码 算法
C#.NET逃逸时间算法生成分形图像的毕业设计完成!晒晒功能
该文介绍了一个使用C#.NET Visual Studio 2008开发的程序,包含错误修复的Julia、Mandelbrot和优化过的Newton三种算法,生成色彩丰富的分形图像。作者改进了原始算法的效率,将内层循环的画点操作移至外部,提升性能。程序提供五种图形模式,支持放大缩小及颜色更新,并允许用户自定义画布大小以调整精度。还具备保存为高质JPG的功能。附有四张示例图片展示生成的分形效果。
|
1月前
|
搜索推荐 C#
C#实现选择排序算法
C#实现选择排序算法
22 2
|
1月前
|
搜索推荐 C#
C#实现插入排序算法
C#实现插入排序算法
21 1
|
1月前
|
搜索推荐 C#
C#实现冒泡排序算法
C#实现冒泡排序算法
28 0
|
1月前
|
算法 C#
C# .Net Core bytes转换为GB/MB/KB 算法
C# .Net Core bytes转换为GB/MB/KB 算法
66 0
|
1月前
|
存储 算法 数据处理
C# | 上位机开发新手指南(十一)压缩算法
流式压缩 流式压缩是一种能够实时处理数据流的压缩方式,例如音频、视频等实时传输的数据。 通过流式压缩算法,我们可以边读取边压缩数据,并能够随时输出已压缩的数据,以确保数据的实时性和减少存储和传输所需的带宽。 块压缩 块压缩则是将数据划分为固定大小的块,在每个块内进行独立的压缩处理。块压缩通常适用于文件、存储、传输等离线数据处理场景。 字典压缩 字典压缩是一种基于字典的压缩算法,通过建立一个字典来存储一组重复出现的字符串,并将这些字符串替换成字典中相应的索引,从而减少数据的存储和传输。字典压缩算法可以更好地处理数据中的重复模式,因为它们可以通过建立字典来存储和恢复重复出现的字符串。
68 0
C# | 上位机开发新手指南(十一)压缩算法
|
1月前
|
算法 C# 数据安全/隐私保护
C# | 上位机开发新手指南(十)加密算法——ECC
本篇文章我们将继续探讨另一种非对称加密算法——ECC。 严格的说,其实ECC并不是一种非对称加密算法,它是一种基于椭圆曲线的加密算法,广泛用于数字签名和密钥协商。 与传统的非对称加密算法(例如RSA)不同,ECC算法使用椭圆曲线上的点乘法来生成密钥对和进行加密操作,而不是使用大数分解等数学算法。这使得ECC算法具有相同的安全性和强度,但使用更少的位数,因此在资源受限的环境中具有优势。 ECC算法虽然使用公钥和私钥进行加密和解密操作,但是这些操作是基于点乘法实现的,而不是基于大数分解等算法实现的。因此,ECC算法可以被视为一种非对称加密算法的变体,但是它与传统的非对称加密算法有所不同。
159 0
C# | 上位机开发新手指南(十)加密算法——ECC
|
1月前
|
XML 算法 安全
C# | 上位机开发新手指南(九)加密算法——RSA
RSA的特性 非对称性 RSA算法使用公钥和私钥两个不同的密钥,公钥用于加密数据,私钥用于解密数据。公钥可以公开,任何人都可以使用,而私钥只有密钥持有人可以访问。 安全性 RSA算法基于大数分解难题,即将一个大的合数分解成其质数因子的乘积。由于目前没有有效的算法可以在合理的时间内对大质数进行分解,因此RSA算法被认为是一种安全的加密算法。 可逆性 RSA算法既可以用于加密,也可以用于解密。加密和解密都是可逆的过程,只要使用正确的密钥,就可以还原原始数据。 签名 RSA算法可以用于数字签名,用于验证数据的完整性和真实性。签名过程是将数据使用私钥进行加密,验证过程是将签名使用公钥进行解密。
132 0
C# | 上位机开发新手指南(九)加密算法——RSA