標籤:des style class blog code http
【Joseph問題描述】
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
如何知道(n-1)個人報數的問題的解?對,只要知道(n-2)個人的解就行了。(n-2)個人的解呢?當然是先求(n-3)的情況 ---- 這顯然就是一個反向推算問題!好了,思路出來了,下面寫遞推公式:
令f[i]表示i個人玩遊戲報m退出最後勝利者的編號,最後的結果自然是f[n]
f[1]=0;
f[i]=(f[i-1]+m)%i; (i>1)
有了這個公式,我們要做的就是從1-n順序算出f[i]的數值,最後結果是f[n]。因為實際生活中編號總是從1開始,我們輸出f[n]+1
由於是逐級遞推,不需要儲存每個f[i],程式也是異常簡單:
int main()
{
int n, m, i, s=0;
scanf("%d%d", &n, &m);
for (i=2; i <=n; i++)
s=(s+m)%i;
printf ("The winner is %d/n", s+1);
}
【氡馬的補充】
當n個人時,退出的一定是報到m%n-1的人(有%是因為m可能大於n,經過迴圈才能報到m),由於所有人是一個環,可以認為是從任何地方開始編號的,所以在m%n-1這個人之後的人可以認為編碼都大於他,那麼整個環的編號就是m%n-1到m%n-1+n-1(也就是m%n-1到m%n-2,實際上一個編號是m還是m+n或者m+2n都無所謂,只要最終算出來的編號對n模數就是正確的編號了。)
那此人退出後他的下一位,也就是原來報m%n這位的編號將更新為0。相應的後面的編碼都會減少m%n,所以得出公式:
f[n] - m%n = f[n-1]
變形一下公式也就是:
f[n] = (f[n-1] + m) % n
本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/RadonMar/archive/2009/07/14/4346204.aspx
問題描述:已知n個人(以編號1,2,3...n分別表示)圍坐在一張圓桌周圍。從編號為k的人開始報數,數到m的那個人出列;他的下一個人又從1開始報數,數到m的那個人又出列;依此規律重複下去,直到圓桌周圍的人全部出列,求最後一個出列人的編號。
遞迴的力量:最佳化到O(N)
在Donald E. Knuth的《具體數學》中,對m=2的情況使用了遞迴的解決方案,並推出了一個常數運算式,使得此種情況下,演算法的複雜度為常量。同時,這種思路也可以應用於n>2 的情況,但無法得出常數運算式,推廣後的遞迴演算法具體的思路如下:
當n個人圍成一圈並以m為步長第一次報數時,第m個人出列,此時就又組成了一個新的,人數為n-1的約瑟夫環,要求n個人的約瑟夫環問題的解,就依賴於求n-1個人的約瑟夫問題的解,要求n-2個人的約瑟夫問題的解,則依賴於求n-2個人的約瑟夫換問題的解,依次類推,直至求1個人的時候,該問題的解。
讓我們回到問題的原始描述中,m是一個固定的值,即步長;n為一個圈的總人數,k為這個圈第一個報數的人的編號,顯然,n在每次遞迴過程中會減1,而k則可以由m,n來唯一確定,這樣的話,當n=1的時候,我們所確定的當前的k值,就是我們所要求的解。
那麼,我們可列出如下的遞迴式:
P(n, m, k)=1 (i = 1)
P(n, m, k)=(P(i - 1, m, k ) + m - 1) % n + 1; (i > 1)
(此處m需先減1是為了讓模n的值不為0)
這樣,我們可以很輕鬆的將此演算法具體實現。這裡給出它的遞推標記法以方便進下一步討論(C言描述):
long Josephus(long n,long m,long k){ //參數分別為:人數,出圈步長,起使報數位置,
for (long i = 1; i <= n; i++)
k = (k + m - 1) % i + 1;
return k; //返回最後一人的位置
}
顯然,這個演算法的複雜度僅為O(n),相比類比演算法,有了很大的改進。
再最佳化:與人數無關
上面的演算法相比最初的類比演算法效率已經大大提升了,那麼,該演算法還有改進的餘地嗎?
事實上,如果我們觀察上述演算法中的變數k,他的初始值為第一個出圈人的編號,但在迴圈的過程中,我們會發現它常常處在一種等差遞增的狀態,我來看這個式子:k = (k + m - 1) % i + 1,可以看出,當i比較大而k+m-1比較小的時候,k就處於一種等差遞增的狀態,這個等差遞增的過程並不是必須的,可以跳過。
我們設一中間變數x,列出如下等式:
k + m * x – 1 = i + x
解出x,令k = k + m * x,將i + x直接賦值給 i,這樣就跳過了中間共x重的迴圈,從而節省了等差遞增的時間開銷。
可是其中求出來的x + i可能會超過n,這樣的結果事實上已經告訴我們此時可以直接結束演算法了,即:
k = k + m * (n - i) ;
i = n;
結束。
另外對於m = 1的情況可以單獨討論:
當k == 1時,最終結果就是n;
當k != 1時,最終結果就是(k + n - 1) % n。
整個演算法的C語言描述如下:
long Josephus( long n, long m, long k ){ //分別為:人數,出圈步長,起使報數位置,
if (m == 1)
k = k == 1 ? n : (k + n - 1) % n;
else{
for (long i = 1; i <= n; i++){
if ((k + m) < i){
x = (i - k + 1) / (m - 1) - 1;
if (i + x < n){
i = i + x;
k = (k + m * x);
}
else{
k = k + m * (n - i) ;
i = n;
}
}
k = (k + m - 1) % i + 1;
}
}
return k; //返回最後一人的位置
}
該演算法的演算法複雜度在m<n時已經與一個圈中的人數n沒有關係了,即使在n=2000000000,m=3,k=1的情況下,也只做了54次迴圈,事實上,大多數的情況都是m<n,且m相對來說很小,此時,這個演算法的複雜度僅為O(m);但當而m>=n時,用方程求出的值不能減少迴圈重數,演算法複雜度仍為O(n)。
解答約瑟夫環問題的幾個方法 問題描述:約瑟夫環
有編號從1到N的N個人坐成一圈報數,報到M的人出局,下一位再從1開始, 如此持續,直止剩下一位為止,報告此人的編號X。輸入N,M,求出X。
常規的解法:用所有的元素產生一個迴圈鏈表,第一次從第一個向前走M步,將當前元素分離出鏈表,然後從下一個元素開始走M步,再將當前元素分離出鏈表,重複以上過程,直到鏈表中只有一個元素時即為所求.
遞迴的解法:
1int f(int n, int m)
2{
3 if (n > 1)
4 return (m + f(n - 1, m)) % m;
5 else
6 return 0;
7}
8非遞迴的解法,很巧妙:
1int f(int n, int m)
2{
3 int i, r = 0;
4 for (i = 2; i <= n; i++)
5 r = (r + m) % i;
6 return r+1;
7}