1. 程式人生 > >約瑟夫環演算法用面向物件的一種實現

約瑟夫環演算法用面向物件的一種實現

首先介紹下約瑟夫環:

約瑟夫環是一個數學的應用問題:已知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;
	}

}