约瑟夫环算法用面向对象的一种实现

首先介绍下约瑟夫环:

约瑟夫环是一个数学的应用问题:已知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;
	}

}

 

    原文作者:约瑟夫环问题
    原文地址: https://blog.csdn.net/thc1987/article/details/28267437
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞