Josephus(約瑟夫)環問題的數學方法,使用遞推公式。
阿新 • • 發佈:2019-02-09
無論是用連結串列實現還是用陣列實現都有一個共同點:要模擬整個遊戲過程,不僅程式寫起來比較煩,而且時間複雜度高達O(nm),當n,m非常大(例如上百萬,上千萬)的時候,幾乎是沒有辦法在短時間內出結果的。我們注意到原問題僅僅是要求出最後的勝利者的序號,而不是要讀者模擬整個過程。因此如果要追求效率,就要打破常規,實施一點數學策略。
∵ k=m%n;
∴ x' = x+k = x+ m%n ; 而 x+ m%n 可能大於n
∴x'= (x+ m%n)%n = (x+m)%n
得到 x‘=(x+m)%n
如何知道(n-1)個人報數的問題的解?對,只要知道(n-2)個人的解就行了。(n-2)個人的解呢?當然是先求(n-3)的情況 ---- 這顯然就是一個倒推問題!好了,思路出來了,下面寫遞推公式:
令f表示i個人玩遊戲報m退出最後勝利者的編號,最後的結果自然是f[n].
遞推公式:
f[1]=0;
f[i]=(f[i-1]+m)%i; (i>1)
有了這個公式,我們要做的就是從1-n順序算出f的數值,最後結果是f[n]。我們輸出f[n]由於是逐級遞推,不需要儲存每個,程式也是異常簡單:(注意編號是0 -- n-1)
為了討論方便,先把問題稍微改變一下,並不影響原意:問題描述:n個人(編號0~(n-1)),從0開始報數,報到m-1的退出
,剩下的人繼續從0開始報數。求勝利者的編號。我們知道第一個人(編號一定是(m-1)%n) 出列之後,剩下的n-1個人組成了一個新的約瑟夫環(以編號為k=m%n的人開始):
public class JOSEPHUS_Deep {
public static void main(String[] args) {
int n, m, i, s=0;
m=5;n=6;
for (i=2; i<=n; i++) {
//因為是從兩個人開始計算,所以i=2,而不是0
s=(s+m)%i;
}
System.out.println("The winner is "+(s+1));
}
}
開始我看這段文字的時候都沒怎麼懂,可能是數學太差的原因吧!
後來我就一句一句來看,結合例項:0 1 2 3 4 5 m=5.
移除4過後,的狀態變成了:5 0 1 2 3. k=m%n=5
如下表
現在我們把他們的編號做一下轉換:
k | k-5 | k-4 | k-3 | k-2 | |
X’ | 5 | 0 | 1 | 2 | 3 |
轉換關係 :X’=(x+k)%n(此例中:n=6) | |||||
x | 0 | 1 | 2 | 3 | 4 |
於是呢,n個人的問題就變成了n-1個人的問題。
遞推公式:
f[1]=0; //就是說如果只有一個人,那麼最後的勝利者就是他,他的編號為0
f[i]=(f[i-1]+m)%i; (i>1) //如果不止一個人,有i個人,那麼就需要求出i-1個人的最後勝利的結果f[i-1],然後呢,利用遞推公式x‘=(x+m)%n 就可以知道第i個的最後結果了。
例如:程式中有這樣兩句:s=0;s=(s+m)%i;
就是說:s=0表示只有一個人的結果。
計算第2個人的結果需要用到第1個人的結果
當i=2,m=5,得到s=1,表示2個人轉圈的結果
當i=3,m=5,得到s=0,表示3個人轉圈的結果。
最後,由於我們選擇6個人轉圈編號一般習慣為1 2 3 4 5 6 ,並不是0 1 2 3 4 5 6
所以輸出結果需要s+1。
我現在想不懂的是,怎麼來確定第i個人的出列編號……它列出是的最後的人!
k --> 0 k+1 --> 1 k+2 --> 2 ... ... k-3 --> n-3 k-2 --> n-2