C#實(shí)現(xiàn)搶紅包算法的示例代碼
二倍均值法(公平版)
發(fā)出一個(gè)固定金額的紅包,由若干個(gè)人來搶,需要滿足哪些規(guī)則?
1.所有人搶到金額之和等于紅包金額,不能超過,也不能少于。
2.每個(gè)人至少搶到一分錢。
3.要保證所有人搶到金額的幾率相等。
假設(shè)剩余紅包金額為M,剩余人數(shù)為N,那么有如下公式:
每次搶到的金額 = 隨機(jī)區(qū)間 (0, M / N × 2)
這個(gè)公式,保證了每次隨機(jī)金額的平均值是相等的,不會(huì)因?yàn)閾尲t包的先后順序而造成不公平。舉個(gè)例子:
假設(shè)有10個(gè)人,紅包總額100元。100/10×2 = 20, 所以第一個(gè)人的隨機(jī)范圍是(0,20 ),平均可以搶到10元。
假設(shè)第一個(gè)人隨機(jī)到10元,那么剩余金額是100-10 = 90 元。90/9×2 = 20, 所以第二個(gè)人的隨機(jī)范圍同樣是(0,20 ),平均可以搶到10元。
假設(shè)第二個(gè)人隨機(jī)到10元,那么剩余金額是90-10 = 80 元。80/8×2 = 20, 所以第三個(gè)人的隨機(jī)范圍同樣是(0,20 ),平均可以搶到10元。
以此類推,每一次隨機(jī)范圍的均值是相等的。
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>
/// 產(chǎn)生紅包數(shù)組
/// </summary>
/// <param name="cashCount">紅包總金額,單位分</param>
/// <param name="peopleNumber">紅包人數(shù)</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;
}
例如,產(chǎn)生的結(jié)果如下:
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是一個(gè)偽隨機(jī)數(shù)生成器,在大多數(shù) Windows 系統(tǒng)上,Random 類 (System) | Microsoft Docs 15 毫秒內(nèi)創(chuàng)建的對象可能具有相同的種子值。因此,如果New Random在循環(huán)中使用,就必須提供隨機(jī)的種子值。我們可以使用RNGCryptoServiceProvider 類 (System.Security.Cryptography) | Microsoft Docs類產(chǎn)生隨機(jī)樹種子。具體代碼如下:
/// <summary>
/// 產(chǎn)生加密的隨機(jī)數(shù)種子值
/// </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);
}
線段切割法(手速版)
算法思路如下:
線段分割法就是把紅包總金額想象成一條線段,而每個(gè)人搶到的金額,則是這條主線段所拆分出的子線段。
當(dāng)N個(gè)人一起搶紅包的時(shí)候,就需要確定N-1個(gè)切割點(diǎn)。
因此,當(dāng)N個(gè)人一起搶總金額為M的紅包時(shí),我們需要做N-1次隨機(jī)運(yùn)算,以此確定N-1個(gè)切割點(diǎn)。
隨機(jī)的范圍區(qū)間是(1, M)。當(dāng)所有切割點(diǎn)確定以后,子線段的長度也隨之確定。這樣每個(gè)人來搶紅包的時(shí)候,只需要順次領(lǐng)取與子線段長度等價(jià)的紅包金額即可。
需要注意一下兩點(diǎn):
1.每個(gè)人至少搶到一分錢。
2.分割的線段如果重復(fù)需要重新切割
具體代碼如下:
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();
}
}
輸出結(jié)果如下:
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
到此這篇關(guān)于C#實(shí)現(xiàn)搶紅包算法的示例代碼的文章就介紹到這了,更多相關(guān)C# 搶紅包算法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#控件picturebox實(shí)現(xiàn)畫圖功能
這篇文章主要為大家詳細(xì)介紹了C#控件picturebox實(shí)現(xiàn)畫圖功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-09-09
Unity3D UGUI實(shí)現(xiàn)縮放循環(huán)拖動(dòng)卡牌展示效果
這篇文章主要為大家詳細(xì)介紹了Unity3D UGUI實(shí)現(xiàn)縮放循環(huán)拖動(dòng)展示卡牌效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-02-02
C#使用FileSystemWatcher控件實(shí)現(xiàn)的文件監(jiān)控功能示例
C#基于WebSocket實(shí)現(xiàn)聊天室功能
C#獲取Excel文件所有文本數(shù)據(jù)內(nèi)容的示例代碼

