从1开始学Java数据结构与算法——用单向环形链表解决Josephu问题

从1开始学Java数据结构与算法——用单向环形链表解决Josephu问题

Josephu问题

设编号为1、2…n的n个人围坐一圈,约定编号为k(1<=k<=n)的人开始报数,数到第m个人出列,然后从他的下一位又开始报数,数到第m个人出列,直到所有人出列位为止,由此产生一个出队编号序列。
解决方法:
采用循环链表,如下图解:
在这里插入图片描述
假设有8个人,从3号开始包数,每次数4个就出列,那么如上图,第一个出列的就是6号,接着从七号开始继续数四个,那么就是2号出列。
依此类推:出列顺序就是6、2、7、4、3、5、1、8

创建单项环形链表的思路分析:

1.创建一个节点类和一个链表类
2.接着在链表类中创建一个节点,让一个节点变量first指向该节点,并独立成环,也就是令first.next = first(这里的first始终指向环形链表的第一个节点)
3.后面每当我们创建一个节点,就将节点加入到已有的环形链表中即可
4.在链表类中编写一个具体的方法去解决Josephu问题

详细思路分析:

1.遍历:
因为first需要始终指向环形链表的第一个节点,所以我们需要一个辅助节点变量curBoy来进行遍历,并初始化指向first(表示从第一个节点开始遍历),接着进行while遍历即可,结束标志就是curBoy.next == first
在这里插入图片描述
4.加入节点
先利用辅助节点变量curBoy遍历后指到要加入位置的前一个节点,接着执行如下三步:
curBoy.next = 新加入的节点(如下左图中的①号线生成,同时②号线解除)
新加入节点.next = first(这里其实就是默认为直接加入到最后一个位置)(如下左图中的③号线生成)
curBoy = 新加入的节点(这里千万注意,一定要让辅助节点变量后移,始终指向最后一个节点)
在这里插入图片描述
5.删除节点(节点出列):
1.根据用户的输入条件(输入从第n个孩子开始报数,每次数m个数),开始让节点逐个出队
2.这里注意,因为每次有一个节点出列之后,下一个节点又作为第一个节点开始报数,所以该情况下first节点变量是可以动的。但是因为为单向的环形链表,所以我们除了需要通过first先移动到出列的节点,还需要一个辅助节点helper变量,始终指向first的前一个节点。接着执行如下三步:
先让first和helper节点变量都移动(n-1)次[为了找到第一个开始报数的孩子]
再让first和helper都移动(m-1)次[为了找到每次要出列的孩子]
接着执行出列:first = first.next;helper.next = first
在这里插入图片描述
结果如图
在这里插入图片描述
3.结束标志:当该单向环形队列只剩一个节点的时候可以停止(help == first)

具体代码实现

首先肯定需要一个表示节点信息的类,这里就只包含了一个编号和next域

/**
 * 节点类
 * @author centuowang
 * @param
 * 		no:节点编号
 * 		next:next域,指向下一个节点
 */
class Boy {
	//这里因为类变量是私有的,所以用get和set方法来对变量进行存取
	private int no;
	private Boy next;
	
	//构造函数
	public Boy(int no) {
		this.no = no;
	}
	
	public int getNo() {
		return no;
	}
	public void setNo(int no) {
		this.no = no;
	}
	public Boy getNext() {
		return next;
	}
	public void setNext(Boy next) {
		this.next = next;
	}
	
}
}

接着就可以开始编写我们的链表类了,在里面编写具体的解决Josephu问题的方法

/**
 * 单项环形链表类
 * @author centuowang
 * @param
 * 		first:始终指向第一个节点	
 * @method
 * 		createLinkList(int num):构造链表方法,这里直接传入需要构造的单向环形链表的节点个数,直接循环生成链表
 * 		show():遍历显示链表
 * 		josephu(int start, int count, int num):出列方法
 */
class CircleLinklist{
	
	//因为在多个方法中都需要该first变量,且都始终表示指向第一个节点,所以放在成员变量的位置
	private Boy first = null;
	
	//构造链表方法,这里直接传入需要构造的单向环形链表的节点个数,直接循环生成链表
	public void createLinkList(int num) {
		//先对传入的参数做个简单的校验
		if(num <1) {
			System.out.println("传入的节点个数不正确");
			return;
		}
		Boy curBoy = null;//创建辅助变量
		for(int i = 1; i <= num; i++) {
			Boy boy = new Boy(i);
			//如果是第一个节点
			if(i == 1) {
				first = boy;//让first指向第一个节点
				boy.setNext(boy);//单个节点强行成环
				curBoy = first;//辅助变量初始化,也指向第一个节点
			}else {
				//不是第一个节点
				curBoy.setNext(boy);
				boy.setNext(first);
				curBoy = boy;//注意辅助变量的后移
			}
			
		}
	}
	
	//遍历显示
	public void show() {
		//先判断链表是否为空
		if(first == null) {
			System.out.println("该链表为空");
		}else {
			//创建辅助变量,并初始化指向第一个节点
			Boy curBoy = first;
			//开始循环遍历输出
			while(true) {
				System.out.printf("小孩的编号为:%d\n",curBoy.getNo());
				if(curBoy.getNext() != first) {
					curBoy = curBoy.getNext();//后移
				}else {
					break;
				}
			}
		}
	}
	
	/**
	 * 计算出列函数
	 * @param
	 * 		start:从第几个节点开始报数
	 * 		count:每次报多少个数
	 * 		num:最初有多少个小孩在里面
	 */
	
	public void josephu(int start, int count, int num) {
		if(first == null || start<1 || start > num) {
			//对开始的位置做个简单的数据校验
			System.out.println("参数输入不正确,请重新输入");
			return;
		}
		//创建辅助变量
		Boy helper = first;
		//因为first一开始指向第一个节点,所以先让该辅助变量指向最后一个节点
		while(true) {
			if(helper.getNext() == first) {
				break;
			}
			//后移
			helper = helper.getNext();
		}
		//让两个节点变量移动到开始报数的节点位置,和它的前一个位置
		for(int i=1; i<start; i++) {
			first = first.getNext();
			helper = helper.getNext();
		}
		//开始执行报数出列的循环过程
		while(true) {
			if(helper == first) {
				//如果只剩下一个节点了,就停止
				break;
			}
			//开始执行报数,让两个节点变量往后移count个位置,但实际只需要移动count-1次即可
			for(int j = 1; j < count; j++) {
				first = first.getNext();
				helper = helper.getNext();
			}
			//开始执行出列
			System.out.printf("第%d个小孩出列\n", first.getNo());
			first = first.getNext();
			helper.setNext(first);
		}
		System.out.printf("最后留下的小孩的编号为:%d\n", first.getNo());
	}
	
}

小问题:

在链表类中具体编写的时候,我们会发现createLinkList(int num)方法的参数num与josephu(int start, int count, int num)的第三个参数num必须一致,都是用来确定该单向环形链表中有几个节点,但是这个地方我们的代码就写的不是那么好,在两个个方法中有着一样的值得参数,如果将他抽离出来可能会更好,发生错误的概率就会小一些

下一篇: 从1开始学Java数据结构与算法——栈的实现与应用.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java大魔王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值