問題描述:已知n個人(以編號1,2,3...n分別表示)圍坐在一張圓桌周圍。從編號為k的人開始報數,數到m的那個人出列;他的下一個人又從1開始報數,數到m的那個人又出列;依此規律重復下去,直到圓桌周圍的人全部出列。求最后剩下的人的初始編號。
可以把問題轉換成:n個人(編號0~(n-1)),從0開始報數,報到(m-1)的退出,剩下的人繼續從0開始報數。求勝利者的編號。則所得的解加1即為原問題的解;
一般我們采用一個循環隊列來模擬約瑟夫環的求解過程,但是如果n比較大的時候,采用模擬的方式求解,需要大量的時間來模擬退出的過程,而且由於需要占用大量的內存空間來模擬隊列中的n個人,並不是一個很好的解法。
在大部分情況下,我們僅僅需要知道最后那個人的編號,而不是要來模擬一個這樣的過程,在這種情況下,可以考慮是否存在着一種數學公式能夠直接求出最后那個人的編號。
我們知道第一個人(編號一定是m%n-1) 出列之后,剩下的n-1個人組成了一個新的約瑟夫環(以編號為k=m%n的人開始):
我們先看第一個人出列后的情況,顯而易見,第一個出列的人的編號一定是m%n-1,這個人出列后,剩下的n-1個人組成了一個新的約瑟夫環,這個約瑟夫環的第一個人在最開始的環中的編號是k=m%n(就是第一個出列的人的下一個)
k k+1 k+2 ... n-2, n-1, 0, 1, 2, ... k-2並且從k開始報0。
事實上,第一個出列的人的編號 m%n-1==k-1,在他被出列后,剩下的人又映射成為一個新的環:
原編號 新編號
k --- 0
k+1 --- 1
k+2 --- 2
... ....
n-1 --- n-k-1
0 --- n-k
1 --- n-k+1
... ...
k-3 --- n-3
k-2 --- n-2
(k-1已經被出列)
--------------------------------------------------一個循環
可以看出,這就是原問題中把n替換成n-1的情況,設最終勝利的那個人在這種編號環境里(已經出列一個元素,編號范圍為0 ------- n-2)的編號為x,則我們可以求出這個人在原編號環境(初始編號范圍 0----n-1)下的編號(x+k)%n.
我們用f(n)表示n個人的情況(編號環境)下的勝利者的編號,則我們要求的為f(n);
那么如何知道n-1個人下面的這個x呢,yes,就是n-2個人情況下得到的x'倒推回去,那么如何知道n-2情況下的x'呢,當然是求n-3個人,這就是一個遞歸的過程 f(n) = (f(n-1)+m)%n.
當最后剩下一個人(最新編號為0)的時候,無論m為幾,這個人總是勝利者,即f(1)=0;
根據以上推理可以得到遞推式
f(1) = 0
f(n) = (f(n-1)+m)%n
那么我們要求f(n),就從f(1)倒推回去即可.
(注意,我們這個算法是把編號為1---n 用 0----n-1替換的,所以最后求出來的編號+1才是最初問題的解,即f(n)+1).
- int f(int n, int m)
- {
- int r = 0;//即f(1)=0;
- for(int i = 2; i <= n; i++)
- r = (r + m) % i;//即f(i)=[f(i-1)+m]%n;
- return r + 1; //即f(n)=1;
- }</span>
這種方法比模擬的方法快多了,我們在碰到問題的時候,可以想一想是否有數學公式來求解 。
參考出處:Jackie`s blog
