《C算法》讀書筆記 (4):Hello,Joseph!

P67, 3-8 約瑟夫問題

上一篇文章中提到了使用鏈表模擬約瑟夫問題求解。約瑟夫問題是這樣的 :假設有N個人決定選出一名領導,將所有人排成一個圓周,從1編號到N。現在從1開始,數M個人,最後的M出列。重複上述步驟,直到只剩下一個人,該人即爲領導。
首先定義鏈表的數據結構:

typedef struct node *link;
struct node 
{
    int item;
    link next;
};

將node稱爲節點。現要刪除節點p->next,只需要使p->next=p->next->next。

void sim_joseph()       // 鏈表模擬
{
    link p, t;
    t = p = (link)malloc(sizeof(node)), p->item = 1, p->next = p;
    for(int i = 2; i <= n; ++ i)
    {
        p = (p->next = (link)malloc(sizeof(node)));
        p->item = i;
    }
    p->next = t;

    for(int i = 0; i < n - 1; ++ i)     // n - 1 times execute
    {
        for(int j = 0; j < m - 1; ++ j)
            p = p->next;
        //printf("%d is killed\n", p->next->item);
        t = p->next;
        p->next = p->next->next;
        free(t);
    }
    printf("%d remains alive\n", p->item);
    free(p);
}

顯然,該算法的複雜度爲O(NM)
有沒有更好的算法?答案是肯定的。
假設有一個N=6,M=2的樣例,也就是6個人圍成一圈,每次報2個數,直到最後一個人。
樣例的流程如下:

這裏寫圖片描述

爲了便於理解,我們將N=6,5,4時的最後一個人先利用鏈表法計算出來:
N=5,M=2時,最後的結果爲3,記F(5,2)=3
同理得,F(4,2)=1F(6,2)=5
將所有編號減1(爲了計算的簡便性,此時F(5,2)=2F(6,2)=4),分析第一步:

這裏寫圖片描述

在1號選手被淘汰出局後,剩下的五人實際上重新組成了一個新的約瑟夫問題F(5,2) ,唯一的區別就是,新一局的選手0在上一局裏面編號爲2,選手1在上一局編號爲3,以此類推。可以建立一個映射關係:

H(x)=(H˜(x)+M)modN

其中H˜(x) 代表新一局裏面的編號,N 爲當前問題人數

這裏寫圖片描述

易見,在5人問題中最後的勝者2,在6人問題中編號爲4。
同理,在4人問題中最後的勝者1,在5人問題中編號爲3。

如此遞推,存在邊界:1人問題最後勝者爲0。從1人問題依次反推就得到解。
該算法的複雜度爲O(N)

完整的程序如下:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;

typedef struct node *link;
struct node 
{
    int item;
    link next;
};
int n, m;

void linear_joseph()    // 遞推
{
    int ans = 0;
    for(int i = 2; i <= n; ++ i)
    {
        ans = (ans + m) % i;
    }
    printf("linear shows %d remains alive\n", ans + 1);
}

void sim_joseph()       // 鏈表模擬
{
    link p, t;
    t = p = (link)malloc(sizeof(node)), p->item = 1, p->next = p;
    for(int i = 2; i <= n; ++ i)
    {
        p = (p->next = (link)malloc(sizeof(node)));
        p->item = i;
    }
    p->next = t;

    for(int i = 0; i < n - 1; ++ i)     // n - 1 times execute
    {
        for(int j = 0; j < m - 1; ++ j)
            p = p->next;
        //printf("%d is killed\n", p->next->item);
        t = p->next;
        p->next = p->next->next;
        free(t);
    }
    printf("sim shows %d remains alive\n", p->item);
    free(p);
}

int main()
{
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);

    while(scanf("%d%d", &n, &m) != EOF)
    {
        sim_joseph();
        linear_joseph();
    }

    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章