最近關注了CSDN的程序員小灰,前兩天發了個紅包算法看着還蠻有意思的,自己使用C實現一下!(PS:后來才發現早已爛大街了……o(╥﹏╥)o)
規則:
1. 所有人搶到金額之和等於紅包金額,不能超過,也不能少於
2. 每個人至少搶到一分錢
3. 要保證所有人搶到金額的幾率相等
先做好准備:

#include<stdio.h> #include<stdlib.h> #include<time.h> #define random(x) (rand()%x) struct Node { float Money; struct Node *Next; }; typedef struct Node *List; List CreateList(); void Add(float current, List *last); int Find(int money, List L); void Sort(List L); void PrintList(List L); void Algorithm1(float money, int num, List L); void Algorithm2(float money, int num, List L); void Algorithm3(float money, int num, List L); int main() { List L= CreateList(); //Algorithm1(100,10,L); //Algorithm2(100, 10, L); //PrintList(L); Algorithm3(100, 10, L); } List CreateList() { List L; L = (List)malloc(sizeof(List)); L->Next = NULL; return L; } void Add(float current,List *last) { List L = (List)malloc(sizeof(List)); L->Money = current; L->Next = NULL; (*last)->Next = L; (*last) = L; } int Find(int money,List L) { List ptr = L->Next; while (ptr) { if (ptr->Money == money) { return 1; } ptr = ptr->Next; } return 0; } void Sort(List L) { List cur = NULL, tail = NULL; cur= L->Next; while (cur->Next!=tail){ int flag = 1; while (cur->Next != tail){ if (cur->Money > cur->Next->Money) { float tmp = cur->Money; cur->Money = cur->Next->Money; cur->Next->Money = tmp; flag = 0; } cur = cur->Next; } if (flag) break; tail = cur; //下一次遍歷的尾結點是當前結點(仔細琢磨一下里面的道道) cur = L->Next; //遍歷起始結點重置為頭結點 } } void PrintList(List L) { List ptr = L->Next; int i = 0; while (ptr) { printf("第%d人抽中金額:%.2f\n", ++i,ptr->Money); ptr = ptr->Next; } }
講了三個算法
1. 第一個是亂講的不正確的,也寫一下好對比嘛
每次取0.01~剩余金額的一個隨機數作為當前抽紅包人的金額,當最后一個人抽取時,將剩余金額全部給他
分析一下:
假設10人,紅包金額100元
第一人:隨機范圍(0,100),平均可以搶到50元
第二人:隨機范圍(0,50),平均可以搶到25元
第三人:隨機范圍(0,25),平均可以搶到12.5元
以此類推,每一次隨機范圍越來越小

void Algorithm1(float money,int num, List L) { List last = L; srand((int)time(0));//設置隨機數種子,不設置每次隨機數相同 int currentMoeny = money * 100;//將金額乘以100以獲得整數 while (num>1){ int tmp = random(currentMoeny-1)+1;//保證隨機數最小為1 Add(tmp/100.0,&last); currentMoeny -= tmp;//更新當前余額 num--; } Add(currentMoeny / 100.0, &last);//最后一個人抽中剩余全部金額 }
結果如下,這第一個搶的人也太多了吧……
2. 二倍均值法
剩余紅包金額M,剩余人數N,那么:每次搶到金額=隨機(0,M/N*2)
保證了每次隨機金額的平均值是公平的
假設10人,紅包金額100元
第一人:100/10*2=20,隨機范圍(0,20),平均可以搶到10元
第二人:90/9*2=20,隨機范圍(0,20),平均可以搶到10元
第三人:80/8*2=20,隨機范圍(0,20),平均可以搶到10元
以此類推,每次隨機范圍的均值是相等的
缺點:除了最后一次,任何一次搶到的金額都不會超過人均金額的兩倍,並不是任意的隨機

void Algorithm2(float money, int num, List L) { List last = L; srand((int)time(0));//設置隨機數種子,不設置每次隨機數相同 int currentMoeny = money * 100;//將金額乘以100以獲得整數 while (num>1) { int mon = (currentMoeny / num) * 2; int tmp = random(mon-1) + 1;//保證隨機數最小為1 Add(tmp / 100.0, &last); currentMoeny -= tmp;//更新當前余額 num--; } Add(currentMoeny / 100.0, &last);//最后一個人抽中剩余全部金額 }
結果一看,恩……好多了,但是我很想一個人多搶點好不好……o(* ̄︶ ̄*)o
3. 線段分割法
把紅包總金額想象成一條很長的線段,而每個人搶到的金額,則是這條主線段所拆分出的若干子線段。
當N個人一起搶紅包的時候,就需要確定N-1個切割點。
因此,當N個人一起搶總金額為M的紅包時,我們需要做N-1次隨機運算,以此確定N-1個切割點。
隨機的范圍區間是(1, M)。當所有切割點確定以后,子線段的長度也隨之確定。這樣每個人來搶紅包的時候,只需要順次領取與子線段長度等價的紅包金額即可。
這就是線段切割法的思路。在這里需要注意以下兩點:
(1)當隨機切割點出現重復,如何處理 --- 重復了就重新切唄
(2)如何盡可能降低時間復雜度和空間復雜度 --- 這里我用鏈表,犧牲時間換取空間(排了個序),也可以犧牲空間節省時間(大數組)

void Algorithm3(float money, int num, List L) { List last = L; srand((int)time(0));//設置隨機數種子,不設置每次隨機數相同 Add(0,&last);//初始點 int currentMoeny = money * 100;//將金額乘以100以獲得整數 while (num>1) {//產生N-1個點 int tmp = random(currentMoeny - 1) + 1;//保證隨機數最小為1 if (Find(tmp, L)==0) {//該點不存在 Add(tmp, &last); num--; } } Add(currentMoeny,&last);//終結點 Sort(L); int i = 0; List ptr = L->Next; while (ptr->Next) { List tmp = ptr->Next; int money = tmp->Money - ptr->Money; printf("第%d人抽中金額:%.2f\n", ++i, money/100.0); ptr = ptr->Next; } }
苦啊,為了節省空間沒有大數組,需要冒個泡排個序O(n^2)就沒了,ε=(´ο`*)))唉,也可以再優化一點:每次隨機數出來后,按順序插入到鏈表,這樣就節省排序的時間
比如說這樣,時間復雜度是O(n^2),之前多了一個排序所以是:O(n^2)+O(n^2):

int Insert(float money, List L) { List ptr = L->Next; while (ptr->Next){ if (ptr->Next->Money == money) { return 0; } else if (ptr->Next->Money > money) { List tmp = (List)malloc(sizeof(List)); tmp->Money = money; tmp->Next = ptr->Next; ptr->Next = tmp; return 1; } ptr = ptr->Next; } Add(money,&ptr);//目前鏈表最大值加到最后 return 1; } void Algorithm3(float money, int num, List L) { List last = L; srand((int)time(0));//設置隨機數種子,不設置每次隨機數相同 int currentMoeny = money * 100;//將金額乘以100以獲得整數 Add(0,&last);//初始點 Add(currentMoeny, &last);//終結點 while (num>1) {//產生N-1個點 int tmp = random(currentMoeny - 1) + 1;//保證隨機數最小為1 //if (Find(tmp, L)==0) {//該點不存在 // Add(tmp, &last); // num--; //} if (Insert(tmp, L) == 1) { num--; } } /*Sort(L);*/ int i = 0; List ptr = L->Next; while (ptr->Next) { List tmp = ptr->Next; int money = tmp->Money - ptr->Money; printf("第%d人抽中金額:%.2f\n", ++i, money/100.0); ptr = ptr->Next; } }
結果,恩……看着舒服多了,nice……睡覺了~
歡迎來吐槽!(*^▽^*)