約瑟夫環演算法用面向物件的一種實現
阿新 • • 發佈:2019-02-18
首先介紹下約瑟夫環:
約瑟夫環是一個數學的應用問題:已知n個人(以編號1,2,3...n分別表示)圍坐在一張圓桌周圍。從編號為k的人開始報數,數到m的那個人出列;他的下一個人又從1開始報數,數到m的那個人又出列;依此規律重複下去,直到圓桌周圍的人全部出列。下面就來個實際例子:
500(n)個小孩圍成一圈,從第1(k)個開始報數:1,2,3,1,2,3,1,2,3......每次報3(m)的小孩退出.
問最後剩下的那個小孩,在以前500人裡是第幾個?
這裡對應約瑟夫環的變數值分別為:
n=500;k=1;m=3;
下面就用面向物件來模擬這個場景,小孩我們可以看成一個類cass Child{}
小孩有哪些屬性呢?
1.報數(小孩當前應該報什麼數字),2.位置(小孩一開始所在第幾個位置)
理論上只有這兩個屬性,但是既然是模擬現實場景,那麼我們自然而然要考慮到每個小孩的真實處境
小孩圍成一個圈,那就說明每一個小孩兩邊都有小孩,這個隱藏屬性不能忽略了.最後我們的小孩類就產生了
// 定義小孩類 class Child{ public Child(int position){ this.position = position; } public int number; // 小孩當前的報數 public final int position; // 小孩的初始位置,固定不變的 public Child beforeChild; // 小孩前一個孩子 public Child nextChild; // 小孩後一個孩子 }
有沒發現其實這個就是一個Node節點類.
類設計出來了,下面就開始玩遊戲.
首先小孩圍成一個圈,我們可以初始化500個Child物件,然後分別設定他們的左右兩邊的孩子
Child[] children = new Child[n]; // 初始化小孩子,將孩子相互關聯起來 // 可以理解為,手拉手圍成一個圈 // 第一個孩子跟最後一個孩子拉手 for (int i = 0; i <n; i++) { children[i] = new Child(i+1); if(i > 0){ // 第二個人開始 together(children[i-1],children[i]); } if(i == n-1){ // 最後一個 // 關聯到第一個 together(children[i],children[0]); } }
最後一個孩子自然要跟第一個孩子關聯起來.
準備工作做好了,然後開始報數,報3的人退出.這裡遇到的問題有:
1.判斷當前報3的孩子
2.報3的孩子如何退出
3.接下來的孩子又開始報1
4.如何判斷只剩下一個人
第一個問題好辦,直接判斷number==3即可
第二個問題,讓他退出也就是他兩邊沒有小孩了,換句話說就是他左邊的小孩跟他右邊的小孩相關聯起來.
第三個問題,我們讓他下面的小孩重新開始報1就可以,currentChild.nextChild.number = 1;
第四個問題,如果只剩下一個人的話,小孩的nextChild屬性會指向自己,這個思考下不難理解
解決了上述問題,我們的核心程式碼也就出來了:
// 取第一個孩子開始報數
Child currentChild = children[k-1];
currentChild.number = 1; // 第一個孩子當然報1
// 迴圈,一直報數,當剩下1個小孩就停下來
while(true){
// 如果下一個小孩引用的物件是自己說明只剩下一個人了
if(currentChild.nextChild == currentChild){
break; // 停止報數
}
// 如果報數是3,把他的上一個小孩跟他的下一個小孩關聯起來
// 這意味著他失去關聯,退出
if(currentChild.number == m){
together(currentChild.beforeChild,currentChild.nextChild);
// 下一個小孩重新報數1
currentChild.nextChild.number = 1;
}else if(currentChild.number < m){// 如果不是3,轉移到下一個小孩
// 下一個小孩報數+1
currentChild.nextChild.number = (currentChild.number + 1);
}
// 輪到下一個小孩
currentChild = currentChild.nextChild;
}
System.out.println("第"+currentChild.position+"個小孩");
// 相互關聯,手拉手
private static void together(Child before,Child next){
before.nextChild = next;
next.beforeChild = before;
}
到此程式已經設計完畢了,等到迴圈退出時,我們只需要列印當前小孩的position屬性即可.
完整程式碼:
package test;
// 定義小孩類
class Child{
public Child(int position){
this.position = position;
}
public int number; // 小孩當前的報數
public final int position; // 小孩的初始位置,固定不變的
public Child beforeChild; // 小孩前一個孩子
public Child nextChild; // 小孩後一個孩子
}
public class Test {
public static void main(String[] args) {
int n = 500,k = 1,m = 3;
Child[] children = new Child[n];
// 初始化小孩子,將孩子相互關聯起來
// 可以理解為,手拉手圍成一個圈
// 第一個孩子跟最後一個孩子拉手
for (int i = 0; i <n; i++) {
children[i] = new Child(i+1);
if(i > 0){ // 第二個人開始
together(children[i-1],children[i]);
}
if(i == n-1){ // 最後一個
// 關聯到第一個
together(children[i],children[0]);
}
}
// 取第一個孩子開始報數
Child currentChild = children[k-1];
currentChild.number = 1; // 第一個孩子當然報1
// 迴圈,一直報數,只剩下1個小孩就停下來
while(true){
// 如果下一個小孩引用的物件是自己說明只剩下一個人了
if(currentChild.nextChild == currentChild){
break; // 停止報數
}
// 如果報數是3,把他的上一個小孩跟他的下一個小孩關聯起來
// 這意味著他失去關聯,退出
if(currentChild.number == m){
together(currentChild.beforeChild,currentChild.nextChild);
// 下一個小孩重新報數1
currentChild.nextChild.number = 1;
}else if(currentChild.number < m){// 如果不是3,轉移到下一個小孩
// 下一個小孩報數+1
currentChild.nextChild.number = (currentChild.number + 1);
}
// 輪到下一個小孩
currentChild = currentChild.nextChild;
}
System.out.println("第"+currentChild.position+"個小孩"); // 第436個小孩
}
// 相互關聯,手拉手
private static void together(Child before,Child next){
before.nextChild = next;
next.beforeChild = before;
}
}