159-解決約瑟夫環的問題(兩種方法)
阿新 • • 發佈:2021-01-30
題目如下:
據說著名猶太曆史學家 Josephus 有過以下故事:在羅馬人佔領喬塔帕特後,39 個猶太人與 Josephus 及他的朋友躲到一個洞中,39 個猶太人決定寧願死也不要被敵人抓到,於是決定了一種自殺方式,41 個人排成一個圓圈,由第 1 個人開始報數,報數到 3 的人就自殺,然後再由下一個人重新報 1,報數到 3 的人再自殺,這樣依次下去,直到剩下最後一個人時,那個人可以自由選擇自己的命運。這就是著名的約瑟夫問題。現在請用單向環形連結串列得出最終存活的人的編號。
n 表示環形連結串列的長度, m 表示每次報數到 m 就自殺
解題方法1(遞迴)
如果只求最後一個報數勝利者的話,我們可以用數學歸納法解決該問題,為了討論方便,先把問題稍微改變一下,並不影響原意:
我們知道第一個人(編號一定是(m%n)-1) 出列之後,剩下的n-1個人組成了一個新的約瑟夫環(以編號為k=m%n的人開始):
k k+1 k+2 … n-2, n-1, 0, 1, 2, … k-2並且從k開始報0。
現在我們把他們的編號做一下轉換:
k --> 0
k+1 --> 1
k+2 --> 2
…
…
k-2 --> n-2
k-1 --> n-1
變換後就完完全全成為了(n-1)個人報數的子問題,假如我們知道這個子問題的解: 例如x是最終的勝利者,那麼根據上面這個表把這個x變回去不剛好就是n個人情況的解嗎?!!變回去的公式很簡單,相信大家都可以推出來:x’=(x+k)%n
遞推公式:
f[1]=0;
f[i]=(f[i-1]+m)%i; (i>1)
有了這個公式,我們要做的就是從n-1順序算出f[i]的數值,最後結果是f[n]。 因為實際生活中編號總是從1開始,我們應該輸出f[n]+1。
#include<stdio.h>
int LastRemaining_Solution(unsigned int n, unsigned int m)
{
if(n==0)
return -1;
if(n==1)
return 0;
else
return (LastRemaining_Solution(n-1,m)+m)%n;
}
int main()
{
printf("%d\n",LastRemaining_Solution(5,3));
//輸出結果為編號3,也就是第4個人
return 0;
}
方法2(常規方法)
#include <stdio.h>
#include <stdlib.h>
//約瑟夫環
int JosephProblem(int n)
{
//建立n個標記
int *arr = (int *)calloc(n,sizeof(int));
//預設每個都是0 ,0表示還在遊戲,1表示退出
int i = 0;//arr下標
int count = n;//還在遊戲的人數
int tmp = 0;//報數器
while(count > 1)//有1個以上的人在遊戲
{
if(arr[i] == 0)
{
tmp++;
if(tmp == 3)//退出遊戲
{
arr[i] = 1;
count--;//還在遊戲的人數減一
tmp = 0;//報數器還原
}
}
i = (i+1)%n;//此時使用i++;是error的 因為我們要處理環形
}
//找到還在遊戲的人
for(i=0;i<n;i++)
{
if(arr[i] == 0)
break;
}
free(arr);
return i+1;
}
int main()
{
int n=JosephProblem(5);
printf("%d\n",n);
//輸出結果為4,也就是第4個人
return 0;
}