約瑟夫環問題 ( 最簡單的數學解法)


基本問題描述:
已知n個人(以編號1,2,3...n分別表示)圍坐在一張圓桌周圍。從編號為1的人開始報數,數到m的那個人出列;他的下一個人又從1開始報數,數到m的那個人又出列;依此規律重復下去,直到圓桌周圍的人全部出列。(也類似於變態殺人狂問題)通常解決這類問題時我們把編號從0~n-1,最后結果+1即為原問題的解。通常,我們會要求輸出最后一位出列的人的序號。那么這里主要研究的是最后一個出列的人的序號要怎么確定。

當n,m數據量很小的時候,我們可以用循環鏈表模擬約瑟夫環的過程。當模擬到人數等於1的時候,輸出剩下的人的序號即可。
具體解法這種方法往往實現起來比較簡單,而且也很容易理解。但是時間復雜度卻是很糟糕的,達到了O(nm),這樣的話,其實在n,m比較大的時候(nm達到10^8或者更大),那么要得出結果往往需要耗費很長的時間,但是我們可以運用一點數學上的技巧,將最后結果推導出來。

為了簡化出列的過程:
首先我們把這n個人的序號編號從0~n-1(理由很簡單,由於m是可能大於n的,而當m大於等於n時,那么第一個出列的人編號是m%n,而m%n是可能等於0的,這樣編號的話能夠簡化后續出列的過程),當數到m-1的那個人出列,因此我們編號完成之后,開始分析出列的過程:
第一次出列:
一開始的時候,所有人的編號排成序列的模式即為:
0,1,2,3,4,5...n-2,n-1
那么第一次出列的人的編號則是(m-1)%n1,那么在第一個人出列之后,從他的下一個人又開始從0開始報數,為了方便我們設k1 = m%n1(n1為當前序列的總人數)那么在第一個人出列之后,k1則是下一次新的編號序列的首位元素,那么我們得到的新的編號序列為:
k1,k1+1,k1+2,k1+3...n-2,n-1,0,1,2...k1-3,k1-2 (k1-1第一次已出列)
那么在這個新的序列中,第一個人依舊是從0開始報數,那么在這個新的序列中,每個人報的相應數字為:
0,1,2,3....n-2
那么第二次每個人報的相應數字與第一次時自己相應的編號對應起來的關系則為:
0 --> k1
1 --> k1+1
2 --> k1+2
...
n-2 ---> (k1+n-2)%n1(n1為當前序列的總人數,因為是循環的序列,k1+n-1可能大於總人數)
那么這時我們要解決的問題就是n-1個人的報數問題(即n-1階約瑟夫環的問題)
可能以上過程你還是覺得不太清晰,那么我們重復以上過程,繼續推導剩余的n-1個人的約瑟夫環的問題:
那么在這剩下的n-1個人中,我們也可以為了方便,將這n-1個人編號為:
0,1,2,3,4...n-2
那么此時出列的人的編號則是(m-1) % n2(n2為當前序列的總人數),同樣的我們設k2 = m % n2,那么在這個人出列了以后,序列重排,重排后新的編號序列為:
k2,k2+1,k2+2,k2+3...n-2,n-1,0,1,2...k2-3,k2-2 (k2-1第一次已出列)
那么在這個新的序列中,第一個人依舊是從1開始報數,那么在這個新的序列中,每個人報的相應數字為:
1,2,3,4....n-2
那么這樣的話是不是又把問題轉化成了n-2階約瑟夫環的問題呢?
后面的過程與前兩次的過程一模一樣,那么遞歸處理下去,直到最后只剩下一個人的時候,便可以直接得出結果
當我們得到一個人的時候(即一階約瑟夫環問題)的結果,那么我們是否能通過一階約瑟夫環問題的結果,推導出二階約瑟夫環的結果呢?
借助上面的分析過程,我們知道,當在解決n階約瑟夫環問題時,序號為k1的人出列后,剩下的n-1個人又重新組成了一個n-1階的約瑟夫環,那么
假如得到了這個n-1階約瑟夫環問題的結果為ans(即最后一個出列的人編號為ans),那么我們通過上述分析過程,可以知道,n階約瑟夫環的結果
(ans + k)%n(n為當前序列的總人數),而k = m%n
則有:
n階約瑟夫環的結果

(ans + m % n)%n,那么我們還可以將該式進行一下簡單的化簡:

當m<n時,易得上式可化簡為:(ans + m)% n

而當m>=n時,那么上式則化簡為:(ans % n + m%n%n)% n
即為:(ans % n + m%n)% n
而 (ans + m)% n = (ans % n + m%n)% n
因此得證
(ans + m % n)%n = (ans + m)% n
這樣的話,我們就得到了遞推公式,由於編號是從0開始的,那么我們可以令
f[1] = 0; //當一個人的時候,出隊人員編號為0
f[n] = (f[n-1] + m)%n //m表示每次數到該數的人出列,n表示當前序列的總人數
而我們只需要得到第n次出列的結果即可,那么不需要另外聲明數組保存數據,只需要直接一個for循環求得n階約瑟夫環問題的結果即可
由於往往現實生活中編號是從1-n,那么我們把最后的結果加1即可。

代碼:

#include <stdio.h>
int main()
{
    int n, m, i, s = 0;
    printf ("N M = ");
    scanf("%d%d", &n, &m);
    for (i = 2; i <= n; i++)
    {
        s = (s + m) % i;
        printf("%d\n",s);
    }
    printf ("\nThe winner is %d\n", s+1);
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM