搶紅包算法


最近關注了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;
    }
}
View Code

講了三個算法

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);//最后一個人抽中剩余全部金額
}
View Code

結果如下,這第一個搶的人也太多了吧……

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);//最后一個人抽中剩余全部金額
}
View Code

結果一看,恩……好多了,但是我很想一個人多搶點好不好……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;
    }
}
View Code

苦啊,為了節省空間沒有大數組,需要冒個泡排個序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;
    }
}
View Code

結果,恩……看着舒服多了,nice……睡覺了~

 

歡迎來吐槽!(*^▽^*)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM