約瑟夫環問題(基本)


n個人圍成圈,依次編號為1,2,..,n,現在從1號開始依次報數,當報到m時,報m的人退出,下一個人重新從1報起,循環下去,問最后剩下那個人的編號是多少?

遞歸法

參見百度百科:Josephus(約瑟夫)問題的數學方法

遞推式:

            fc1

將這些人的編號用對總人數取模所得余數代替(即原編號減一),其中 Fn 表示n個人的環,最后剩下的那個人的編號。

由這個遞推式不難寫出相應代碼:

/* 約瑟夫環問題 */
# include <stdio.h>
int main()
{
    int m, n, i, s;
 
    while (~scanf("%d%d", &m, &n))
    {
        s = 0;          // F1 = 0;
        for (i = 2; i <= n; ++i)
            s = (s + m) % i;
        printf("%d\n", s+1);       // 原問題的編號是從1開始的
    }
 
    return 0;
}

上述代碼的復雜度為O(n)。

模擬法

采用循環鏈表進行模擬題目描述的過程,退出通過刪除節點表示;

使用鏈表模擬時,維護一個充當報數角色的變量 i ,每當 i 等於 m 時,重置 i 為 1;

循環終止的條件是只剩余一個節點(p->next = p),或者已經刪除了 n-1 個節點;

由於每次刪除一個節點需要報夠 m 次數(遍歷 m 個節點),一共要刪除 n-1 個數,所以鏈表模擬的復雜度為O(m*n)。

/* 約瑟夫環問題——鏈表模擬*/
# include <stdio.h>
# include <stdlib.h>
 
typedef struct node
{
    int id;
    struct node* next;
} node;
 
int main()
{
    int m, n, i;
    node *p, *q, *head;
 
    while (~scanf("%d%d", &m, &n))
    {
        // 創建含 n 個節點的循環鏈表,初始化編號
        head = (node *)malloc(sizeof(node));
        head->id = 1;
        q = head;
        for (i = 2; i <= n; ++i)
        {
            p = (node *)malloc(sizeof(node));
            p->id = i;
            q->next = p;
            q = p;
        }
        q->next = head;
 
        // 模擬退出過程
         i = 1;
        p = head;
        while (p->next != p)
        {
            q = p;
            p = p->next;
            ++i;
            if (i == m)         //delete(p); i = 1;
            {
                q->next = p->next;
                free(p);
                p = q->next;
                i = 1;
            }
        }
 
        // 打印剩余節點的編號
        printf("%d\n", p->id);
       free(p);
    }
 
    return 0;
}

擴展

當最先報數的那個人編號不是 1 時,對於遞歸法,需要對結果進行調整,對於鏈表只需要將循環起點的 head 改成相應編號的指針;

當報數前去掉一個編號為 k 的人,並從 k+1 開始報數時,可以認為從某個人報起,到編號為 k 時,剛好為 m ,因此去掉了這個人,這樣對應上述遞歸方法的結果為:(s+1) + (m-k),並將這個結果的范圍調整到 [1, n] 內。


免責聲明!

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



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