約瑟夫環——公式法(遞推公式)

約瑟夫問題

約瑟夫問題是個著名的問題:N我的圍成一圈,第一我的從1開始報數,報M的將被殺掉,下一我的接着從1開始報。如此反覆,最後剩下一個,求最後的勝利者。
例如只有三我的,把他們叫作A、B、C,他們圍成一圈,從A開始報數,假設報2的人被殺掉。html

  • 首先A開始報數,他報1。僥倖逃過一劫。
  • 而後輪到B報數,他報2。很是慘,他被殺了
  • C接着從1開始報數
  • 接着輪到A報數,他報2。也被殺死了。
  • 最終勝利者是C

解決方案

普通解法

剛學數據結構的時候,咱們可能用鏈表的方法去模擬這個過程,N我的看做是N個鏈表節點,節點1指向節點2,節點2指向節點3,……,節點N-1指向節點N,節點N指向節點1,這樣就造成了一個環。而後從節點1開始一、二、3……往下報數,每報到M,就把那個節點從環上刪除。下一個節點接着從1開始報數。最終鏈表僅剩一個節點。它就是最終的勝利者。
這裏寫圖片描述web

缺點:

要模擬整個遊戲過程,時間複雜度高達O(nm),當n,m很是大(例如上百萬,上千萬)的時候,幾乎是沒有辦法在短期內出結果的。數組

公式法

約瑟夫環是一個經典的數學問題,咱們不難發現這樣的依次報數,彷佛有規律可循。爲了方便導出遞推式,咱們從新定義一下題目。
問題: N我的編號爲1,2,……,N,依次報數,每報到M時,殺掉那我的,求最後勝利者的編號。數據結構

這邊咱們先把結論拋出了。以後帶領你們一步一步的理解這個公式是什麼來的。
遞推公式:
f ( N , M ) = ( f ( N 1 , M ) + M ) % N f(N,M)=(f(N-1,M)+M)\%N app

  • f ( N , M ) f(N,M) 表示,N我的報數,每報到M時殺掉那我的,最終勝利者的編號
  • f ( N 1 , M ) f(N-1,M) 表示,N-1我的報數,每報到M時殺掉那我的,最終勝利者的編號

下面咱們不用字母表示每個人,而用數字。
1 2 3 4 5 6 7 8 9 10 11 一、 2 、 三、 四、5 、六、 7 、 八、 九、 十、 11
表示11我的,他們先排成一排,假設每報到3的人被殺掉。svg

  • 剛開始時,頭一我的編號是1,從他開始報數,第一輪被殺掉的是編號3的人。
  • 編號4的人從1開始從新報數,這時候咱們能夠認爲編號4這我的是隊伍的頭。第二輪被殺掉的是編號6的人。
  • 編號7的人開始從新報數,這時候咱們能夠認爲編號7這我的是隊伍的頭。第三輪被殺掉的是編號9的人。
  • ……
  • 第九輪時,編號2的人開始從新報數,這時候咱們能夠認爲編號2這我的是隊伍的頭。這輪被殺掉的是編號8的人。
  • 下一我的仍是編號爲2的人,他從1開始報數,不幸的是他在這輪被殺掉了。
  • 最後的勝利者是編號爲7的人。

下圖表示這一過程(先忽視綠色的一行)
這裏寫圖片描述spa

如今再來看咱們遞推公式是怎麼獲得的!
將上面表格的每一行當作數組,這個公式描述的是:倖存者在這一輪的下標位置code

  • f ( 1 , 3 ) f(1,3) :只有1我的了,那我的就是獲勝者,他的下標位置是0
  • f ( 2 , 3 ) = ( f ( 1 , 3 ) + 3 ) % 2 = 3 % 2 = 1 f(2,3)=(f(1,3)+3)\%2=3\%2=1 :在有2我的的時候,勝利者的下標位置爲1
  • f ( 3 , 3 ) = ( f ( 2 , 3 ) + 3 ) % 3 = 4 % 3 = 1 f(3,3)=(f(2,3)+3)\%3=4\%3=1 :在有3我的的時候,勝利者的下標位置爲1
  • f ( 4 , 3 ) = ( f ( 3 , 3 ) + 3 ) % 4 = 4 % 4 = 0 f(4,3)=(f(3,3)+3)\%4=4\%4=0 :在有4我的的時候,勝利者的下標位置爲0
  • ……
  • f ( 11 , 3 ) = 6 f(11,3)=6

很神奇吧!如今你還懷疑這個公式的正確性嗎?上面這個例子驗證了這個遞推公式的確能夠計算出勝利者的下標,下面將講解怎麼推導這個公式。
**問題1:**假設咱們已經知道11我的時,勝利者的下標位置爲6。那下一輪10我的時,勝利者的下標位置爲多少?
**答:**其實吧,第一輪刪掉編號爲3的人後,以後的人都往前面移動了3位,勝利這也往前移動了3位,因此他的下標位置由6變成3。orm

**問題2:**假設咱們已經知道10我的時,勝利者的下標位置爲3。那下一輪11我的時,勝利者的下標位置爲多少?
**答:**這能夠看錯是上一個問題的逆過程,你們都日後移動3位,因此 f ( 11 , 3 ) = f ( 10 , 3 ) + 3 f(11,3)=f(10,3)+3 。不過有可能數組會越界,因此最後模上當前人數的個數, f ( 11 , 3 ) = f ( 10 , 3 ) + 3 % 11 f(11,3)=(f(10,3)+3)\%11 xml

**問題3:**如今改成人數改成N,報到M時,把那我的殺掉,那麼數組是怎麼移動的?
**答:**每殺掉一我的,下一我的成爲頭,至關於把數組向前移動M位。若已知N-1我的時,勝利者的下標位置位 f ( N 1 , M ) f(N-1,M) ,則N我的的時候,就是日後移動M爲,(由於有可能數組越界,超過的部分會被接到頭上,因此還要模N),既 f ( N , M ) = ( f ( N 1 , M ) + M ) % n f(N,M)=(f(N-1,M)+M)\%n

**注:**理解這個遞推式的核心在於關注勝利者的下標位置是怎麼變的。每殺掉一我的,其實就是把這個數組向前移動了M位。而後逆過來,就能夠獲得這個遞推式。

由於求出的結果是數組中的下標,最終的編號還要加1

下面給出代碼實現:

int cir(int n,int m)
{
	int p=0;
	for(int i=2;i<=n;i++)
	{
		p=(p+m)%i;
	}
	return p+1;
}

在這裏插入圖片描述