猴子選大王--約瑟夫問題淺析


猴子選大王--約瑟夫問題淺析

  猴子選大王問題是一個十分經典的算法問題,這個問題是這樣的:一堆猴子都有編號,編號是1,2,3 ...m,這群猴子(m個)按照1-m的順序圍坐一圈,從第1開始數,每數到第N個,該猴子就要離開此圈,這樣依次下來,直到圈中只剩下最后一只猴子,則該猴子為大王。

  這個問題要解決起來並不難,但求解的方法很多;題目的變化形式也很多,而我們統稱這類問題為約瑟夫問題。這類題目基本的描述為:N個人圍成一圈,從第一個開始報數,第M個將被殺掉,最后剩下一個,其余人都將被殺掉。例如N=6,M=5,被殺掉的順序是:5,4,6,2,3,1。下面我們先來分析一下解決這類問題的幾個步驟。

  (1)由於對於每個人只有死和活兩種狀態,因此可以用布朗型數組標記每個人的狀態,可用true表示死,false表示活。
  (2)開始時每個人都是活的,所以數組初值全部賦為false。
  (3)模擬殺人過程,直到所有人都被殺死為止。
 
  題目中N個人圍成一圈,因而啟發我們用一個循環的鏈來表示,可以使用數組結構來構成一個循環鏈表。結構中有兩個成員,其一為指向下一個人的指針,以構成環形的鏈;其二為該人是否被殺死的標記,為1表示還存活。從第一個人開始對還存活的人進行計數,每數到M時,將結構中的標記改為0,表示該人已被殺死。這樣循環計數直到有15個人被殺死為止。
  但是,無論是用鏈表實現還是用數組實現都有一個共同點:要模擬整個游戲過程,不僅程序寫起來比較煩,而且時間復雜度高達O(nm),當n,m非常大(例如上百萬,上千萬)的時候,幾乎是沒有辦法在短時間內出結果的。我們注意到原問題僅僅是要求出最后的勝利者的序號,而不是要讀者模擬整個過程。因此如果要追求效率,就要打破常規,實施一點數學策略。
 
  
為了討論方便,先把問題稍微改變一下,並不影響原意:
問題描述:n個人(編號0~(n-1)),從0開始報數,報到(m-1)的退出,剩下的人繼續從0開始報數。求勝利者的編號。
我們知道第一個人(編號一定是(m-1)) 出列之后,剩下的n-1個人組成了一個新的約瑟夫環(以編號為k=m mod 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
變換后就完完全全成為了(n-1)個人報數的子問題,假如我們知道這個子問題的解:例如x是最終的勝利者,那么根據上面這個表把這個x變回去不剛好就是n個人情況的解嗎?!!變回去的公式很簡單,相信大家都可以推出來:x'=(x+k) mod n
如何知道(n-1)個人報數的問題的解?對,只要知道(n-2)個人的解就行了。(n-2)個人的解呢?當然是先求(n-3)的情況 ---- 這顯然就是一個倒推問題!好了,思路出來了,下面寫遞推公式:
令f表示i個人玩游戲報m退出最后勝利者的編號,最后的結果自然是f[n]
遞推公式
f[1]=0;
f[i]=(f[i-1]+m) mod i; (i>1)
有了這個公式,我們要做的就是從1-n順序算出f的數值,最后結果是f[n]。因為實際生活中編號總是從1開始,我們輸出f[n]+1
由於是逐級遞推,不需要保存每個f,程序也是異常簡單:
 1 package com.jredu100.ch4.test;
 2 
 3 import java.util.Scanner;
 4 /**
 5  * 約瑟夫問題
 6  * @author ymyBlogs
 7  *
 8  */
 9 public class Test11 {
10 
11     public static void main(String[] args) {
12         // TODO Auto-generated method stub
13         int s=0;
14         int M=3;
15         Scanner sc=new Scanner(System.in);
16         System.out.println("請輸入人數:");
17         int n=sc.nextInt();
18         for(int i=2;i<=n;i++){
19             s=(s+M)%i;
20         }
21         System.out.println("最終位置為:");
22         System.out.println(s+1);
23     }
24 
25 }

這個算法的時間復雜度為O(n),相對於模擬算法已經有了很大的提高。算n,m等於一百萬,一千萬的情況不是問題了。可見,適當地運用數學策略,不僅可以讓編程變得簡單,而且往往會成倍地提高算法執行效率。

   

       


免責聲明!

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



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