1. 程式人生 > 其它 >159-解決約瑟夫環的問題(兩種方法)

159-解決約瑟夫環的問題(兩種方法)

技術標籤:林澤宇刷題演算法

題目如下:
據說著名猶太曆史學家 Josephus 有過以下故事:在羅馬人佔領喬塔帕特後,39 個猶太人與 Josephus 及他的朋友躲到一個洞中,39 個猶太人決定寧願死也不要被敵人抓到,於是決定了一種自殺方式,41 個人排成一個圓圈,由第 1 個人開始報數,報數到 3 的人就自殺,然後再由下一個人重新報 1,報數到 3 的人再自殺,這樣依次下去,直到剩下最後一個人時,那個人可以自由選擇自己的命運。這就是著名的約瑟夫問題。現在請用單向環形連結串列得出最終存活的人的編號。

n 表示環形連結串列的長度, m 表示每次報數到 m 就自殺

解題方法1(遞迴)

如果只求最後一個報數勝利者的話,我們可以用數學歸納法解決該問題,為了討論方便,先把問題稍微改變一下,並不影響原意:

問題描述:n個人(編號0~(n-1)),從0開始報數,報到(m-1)的退出,剩下的人 繼續從0開始報數。求勝利者的編號。

我們知道第一個人(編號一定是(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[i]表示i個人玩遊戲報m退出最後勝利者的編號,最後的結果自然是f[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;
}