n個人圍成圈,依次編號為1,2,..,n,現在從1號開始依次報數,當報到m時,報m的人退出,下一個人重新從1報起,循環下去,問最后剩下那個人的編號是多少?
遞歸法
參見百度百科:Josephus(約瑟夫)問題的數學方法
遞推式:
將這些人的編號用對總人數取模所得余數代替(即原編號減一),其中 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] 內。