問題描述
已知n個人(以編號1,2,3,...,n分別表示)圍坐在一張圓桌上。指定編號為k的人開始從1報數,數到m的那個人出列;出列那個人的下一位又從1開始報數,數到m的那個人出列;以此規則重復下去,直到圓桌上的人全部出列。
分析解決
解決方法主要有邏輯分析、數學分析法。
邏輯分析:就是按照游戲規則一個個報數,報到m的人出局,結構層次簡單清晰明了。這種方式實現主要采用順序表實現
數學分析:采用數學方式歸納統計分析出每次出局人的規律,直接得出每次出局的人,然后以代碼實現。這種方法需要較強的數學分析能力,代碼較簡單。這種實現主要采用數學公式。
邏輯分析
先以C語言數組為例,由於C語言數組不能像Java、Python這種面向對象語言的數組那樣使用刪除數組元素的方法,每次刪除數組中的一個元素,都要將后面元素前移一位,這里簡單的將刪除的元素值設為0。
假設總人數n=7,從編號k=2的人開始,數到m=3的人出局,下面是分析過程。

1 #include <stdio.h> 2 3 int main(void) 4 { 5 int total; //define total people 6 int out; //define out number 7 int start; //define start number 8 9 /* set the total, out and start number */ 10 printf("Please enter the total people\n"); 11 scanf("%d", &total); 12 printf("Please enter the out number\n"); 13 scanf("%d", &out); 14 printf("Please enter the start position\n"); 15 scanf("%d", &start); 16 17 int people[total]; //init the people 18 int i = start - 1; //init i for people tag 19 int count = 0; //init count for the people out 20 int remain = total; //init the remain number of people 21 22 /* init josephus ring */ 23 int j = 0; 24 for (j = 0; j < total; j++) 25 people[j] = j + 1; 26 27 /* begin to solve josephus problem */ 28 printf("begin to solve josephus's problem.\n"); 29 /* print josephus ring */ 30 for (j = 0; j < total; j++) 31 printf("%d ", people[j]); 32 printf("\n"); 33 34 while (1) 35 { 36 if(people[i] > 0) 37 { 38 count++; 39 }else 40 { 41 i++; 42 if (i == total) 43 i = 0; 44 continue; 45 } 46 47 if(count == out) 48 { 49 printf("The people of %d is out.\n", people[i]); 50 people[i] = 0; 51 count = 0; 52 remain--; 53 } 54 55 i++; 56 if (i == total) 57 i = 0; 58 59 if (remain == 0) 60 break; 61 62 } 63 64 printf("Josephus has solved his problem\n"); 65 66 return 0; 67 }
運行后,結果與上面分析一致
下面給出Java的集合對象實現和Python的列表實現。因為Java集合對象和Python列表都有刪除元素的方法,這里的邏輯和上面稍有不同。
這里配的圖第5個出列后面解釋有點錯誤,大概的意思和第二個出列的處理類似。

1 import java.util.Scanner; 2 import java.util.ArrayList; 3 import java.util.List; 4 5 public class JOSEPHUS { 6 7 public static void main(String[] args) { 8 /*init data*/ 9 Scanner scanner = new Scanner(System.in); 10 System.out.println("Please enter the total people"); 11 int total = scanner.nextInt(); 12 System.out.println("Please enter the out number"); 13 int out = scanner.nextInt(); 14 System.out.println("Please enter the start position"); 15 int start = scanner.nextInt(); 16 17 int count = 0; //define count for people 18 19 /*init josephus ring and print it*/ 20 List<Integer> people = new ArrayList<Integer>(); 21 for (int i = 0; i < total; i++) 22 people.add(i + 1); 23 System.out.println(people); 24 25 int now = start - 1; //Pointer of people who count 26 27 /*Begin to solve josephus problem*/ 28 while(people.size()>0) { 29 count++; 30 if(count == out) { 31 System.out.println("The people of " + people.get(now) + " is out"); 32 people.remove(now); 33 //System.out.println(people); 34 if (now == people.size()) 35 count = 0; 36 else 37 count=1; 38 } 39 40 now++; 41 if(now >= people.size()) 42 now = 0; 43 } 44 } 45 }
代碼運行結果與分析一致,java集合版比C數組版邏輯處理代碼稍微簡單一點點。

1 total = int(input("請輸入總人數:")) 2 out = int(input("請輸入出局號:")) 3 start = int(input("請輸入開始位置:")) 4 5 #初始化約瑟夫環 6 people = [] 7 for i in range(total): 8 people.append(i+1) 9 10 print("需要處理的約瑟夫環為:\n",people) 11 count = 0 #定義報數變量 12 now = start - 1 #now指定當前報數的人 13 while len(people): 14 count += 1 15 if(count == out): 16 print("編號為 %d 的人出局" %people[now]) 17 people.pop(now) 18 if now == len(people): 19 count = 0 20 else: 21 count = 1 22 now += 1 23 if now >= len(people): 24 now = 0
運行結果與分析一致,可以看出C、java和Python用數組實現約瑟夫環,Python的代碼是最短的,C的代碼最長,Java居中。當然代碼都還有優化的地方。
也可以使用環形鏈表實現。
數學分析
總人數有n個人,從k的位置開始報數,報數到m的人出局。
先假設人數n足夠大,不管出局多少個人,都不會回到編號為1的位置。
第一次出局的人的位置為k-1+m,出局人的下一位從新開始報數,這就成了一個新的約瑟夫環,相當於第二次報數的起始位置從k+m開始,總人數為n-1;
第二次出局的人的位置為k+m-1+m,出局人的下一位從新開始報數,這就又成了一個新的約瑟夫環,相當於第三次報數的起始位置從k+2m開始,總人數為n-2;
第三次出局的人的位置為k+2m-1+m,出局人的下一位從新開始報數,這就又成了一個新的約瑟夫環,相當於第四次報數的起始位置從k+3m開始,總人數為n-3;
。。。
第t次出局的人的位置為k+tm-1,出局人的下一位從新開始報數,這就又成了一個新的約瑟夫環,相當於第t+1次報數的起始位置從k+tm開始,總人數為n-t。
因為假設人數n足夠大,不管出局多少個人,都不會回到編號為1的位置,相當與出局后又成了一個新的約瑟夫環,所以(k+tm-1)%(n-t)=(k+tm-1)為出局人的位置。
現在假設k-1+m>n
當編號n報數時還沒有報到m,則從編號1接着編號n報數,相當與n后面還是接着無窮無盡等待報數的人。
假設循環c輪,編號p,報的數是m,編號p要出局,相當於k-1+m=cn+p,即(k-1+m)%cn=p,剩余的人數為n-1。
按上面的方法很容易得出第t次出局的人的編號為(k+tm-1)%(n-t)。
當k-1+m=n時
即編號為n的人報數為m,此時(k+tm-1)%(n-t)=0,重新開始的位置為編號為1的位置。

1 package yuesefu; 2 import java.util.ArrayList; 3 import java.util.List; 4 import java.util.Scanner; 5 6 public class Yue { 7 public static void main(String[] args) { 8 Scanner scanner = new Scanner(System.in); 9 System.out.print("請輸入總人數:"); 10 int total = scanner.nextInt(); 11 System.out.print("請輸入出局號:"); 12 int count = scanner.nextInt(); 13 System.out.print("請輸入開始的編號:"); 14 int start = scanner.nextInt(); 15 16 /*初始化約瑟夫環*/ 17 List<Integer> people = new ArrayList<Integer>(); 18 for (int i = 1; i <= total; i++) { 19 people.add(i); 20 } 21 int k=start-1; 22 System.out.println("約瑟夫環為:"+people); 23 while (people.size()>0) { 24 k = (k + count)%(people.size())-1; 25 if(k<0) { 26 System.out.println("出局人的編號為:" + people.get(people.size()-1)); 27 people.remove(people.size()-1); 28 k=0; 29 }else { 30 System.out.println("出局人的編號為:" + people.get(k)); 31 people.remove(k); 32 } 33 } 34 } 35 }

1 total = int(input("請輸入總人數:")) 2 out = int(input("請輸入出局號:")) 3 start = int(input("請輸入開始位置:")) 4 5 #初始化約瑟夫環 6 people = [] 7 for i in range(total): 8 people.append(i+1) 9 10 print("需要處理的約瑟夫環為:\n",people) 11 now = start - 1 #now指定當前報數的人 12 while len(people): 13 now = (now + out)%len(people)-1 14 if now < 0: 15 print("編號為 %d 的人出局" %people[now]) 16 people.pop(now) 17 now = 0 18 else: 19 print("編號為 %d 的人出局" % people[now]) 20 people.pop(now)
測試的結果也是和前面的測試結果一樣的,可以看到后面程序處理更簡單些,省了很多邏輯判斷環節,這就是數學的魅力。