趣味算法--約瑟夫環問題


問題描述

已知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 }
C 數組版

運行后,結果與上面分析一致

下面給出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集合版

代碼運行結果與分析一致,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
Python列表版

運行結果與分析一致,可以看出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 }
Java 實現
 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)
Python實現

測試的結果也是和前面的測試結果一樣的,可以看到后面程序處理更簡單些,省了很多邏輯判斷環節,這就是數學的魅力。

 


免責聲明!

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



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