【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()
【氡馬的補充】
當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
問題描述:已知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)
josephus(
long
n,long
m,long
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語言描述如下:
josephus(
long
n, long
m, long
k )else
=(k +m
-1) %
i +1;
return
k; //
返回最後一人的位置
}該演算法的演算法複雜度在m=n時,用方程求出的值不能減少迴圈重數,演算法複雜度仍為o(n)。
問題描述:約瑟夫環
有編號從1到n的n個人坐成一圈報數,報到m的人出局,下一位再從1開始, 如此持續,直止剩下一位為止,報告此人的編號x。輸入n,m,求出x。
常規的解法:用所有的元素生成乙個迴圈鍊錶,第一次從第乙個向前走m步,將當前元素分離出鍊錶,然後從下乙個元素開始走m步,再將當前元素分離出鍊錶,重複以上過程,直到鍊錶中只有乙個元素時即為所求.
遞迴的解法: 1
intf(
intn,
intm)2
8 非遞迴的解法,很巧妙: 1
intf(
intn,
intm)2
約瑟夫環非遞迴演算法分析
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,...
約瑟夫環非遞迴演算法和遞迴演算法分析與實現(ZZ)
joseph問題描述 n個人 編號0 n 1 從0開始報數,報到 m 1 的退出,剩下的人繼續從0開始報數。求勝利者的編號。求解思路 size medium b 1.非遞迴演算法 b size 我們知道第乙個人 編號一定是m n 1 出列之後,剩下的n 1個人組成了乙個新的約瑟夫環 以編號為k m ...
約瑟夫環 遞迴演算法
假設下標從0開始,0,1,2 m 1共m個人,從1開始報數,報到k則此人從環出退出,問最後剩下的乙個人的編號是多少?現在假設m 10 0 1 2 3 4 5 6 7 8 9 k 3 第乙個人出列後的序列為 0 1 3 4 5 6 7 8 9 即 3 4 5 6 7 8 9 0 1 我們把該式轉化為 ...