算法(Java实现)-图解Josephu(约瑟夫)问题

1、Josephu(约瑟夫)

问题描述:设编号为1,2,3,,,,n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列,和最后剩下的那个人。求这个序列和最后那个人的编号。

整体解题思路:用一个不带头节点的单向环形链表来处理该问题。

  1. 先构成一个有n个结点的单向环形链表
  2. 由k结点起从1开始计数,计到m时,对应结点从链表中删除
  3. 被删除的下一个节点又从1开始计数,直到最后一个节点从链表中删除

2、构建单向循环链表

2.1单向环形链表示意图:

假设:

  • n=5,即有五个人
  • k=1,从编号为1的人开始报数
  • m=2,数两个数出一个人

预期结果:

2.2具体构建单向环形链表过程

1、先创建第一个节点,让first指向该结点,该节点next指向自己形成环

2、创建一个指向first的辅助变量curBoy帮助构建环形链表,创建新节点boy使curBoy.next先指向boy(即与新节点相连)

3、boy.next指向first使这两个节点构成环,curBoy辅助变量向后移动到新的节点上

4、依次类推后面的节点也是这样操作

2.3遍历环形链表

  1. 先让一个辅助变量curBoy,指向first节点
  2. 然后通过while循环遍历该环形链表即可curBoy.next==first结束

2.4出圈思路分析

1、需要创建一个辅助指针(变量)helper,事先应该指向环形链表的最后一个节点

(小孩报数前,先让first和helper移动k-1次,确保first是指向第一个报数的孩子(图示里一个报数的孩子是1号))

2、当孩子报数时,让first和helper指针同时的移动m-1次(图示中数两个数出一个人即m=2)

3、这时可以将first指向的小孩节点出圈first = first.next ; helper.next = first;原来first指向的节点就没有任何引用,就会被垃圾机制回收

3、代码实现

package com.zhukun.LinkList;

import java.util.Scanner;

//创建一个Boy类,表示一个节点
class Boy{
	private int no;//编号
	private Boy next;//指向下一个节点,默认为null
	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;
	}
}
//创建一个环形的单向链表
class CircleSingleLinkedList{
	//创建一个first节点,当前没有编号
	private Boy first = null;
	//添加小孩节点,构建成一个环形链表
	public void addBoy(int nums)
	{
		//先对nums进行数据校验
		if(nums < 1)
		{
			System.out.println("nums的值不正确");
			return;
		}
		Boy curBoy =null; //因为first指针不能动,让curBoy辅助变量帮助构建环形链表
		//使用for循环来构建环形链表
		for(int i=1;i<=nums;i++)
		{
			//根据编号,创建小孩节点
			Boy boy = new Boy(i);
			//如果第一个小孩,先自己跟自己构成环
			if(i == 1)
			{
				first = boy;
				first.setNext(first);//构成环
				curBoy = first;//让辅助变量curBoy指向第一个小孩
			}
			else {
				curBoy.setNext(boy);//curBoy指向新生成的boy节点,先让后一个节点与前一个节点相连
				boy.setNext(first);//新生成的boy节点指向first,重新构成环
				curBoy = boy;//curBoy向后移动移动到新生成的boy节点上
			}
		}
	}
	//遍历当前的环形
	public void showBoy()
	{
		//判断链表是否为空
		if(first == null)
		{
			System.out.println("没有任何小孩");
			return;
		}
		//因为first不能动,因此我们仍然使用一个辅助指针完成遍历
		Boy curBoy = first;
		while(true)
		{
			System.out.println("小孩的编号:"+curBoy.getNo());
			if(curBoy.getNext() == first) //说明已经遍历完所有小孩
			{
				break;
			}
			curBoy = curBoy.getNext();//辅助变量curBoy遍历完一个往后移动继续向下遍历
		}
	}
		//根据用户的输入,计算小孩出圈的顺序
		//startNo   :表示从第几个孩子开始数数
		//countNum    :表示数几下后出一个孩子
		//nums    :表示最初有多少个孩子在圈子中
	public void countBoy(int startNo,int countNum,int nums)
	{
			//先对输入的数据进行校验
		if(first == null || startNo<1 || startNo>nums)
		{
			System.out.println("参数输入有错误,请重新输入");
			return;
		}
		//创建一个辅助指针,帮助完成小孩出圈
		Boy helper = first;
		//需要创建一个辅助变量helper事先应该指向环形链表的最后这个节点
		while(true)
		{
			if(helper.getNext() == first)
			{
				break;
			}
			helper = helper.getNext();
		}
		//小孩报数前,先让first和helper移动k-1次确保first指向第一个报数的孩子上,而helper指向这种报数顺序下最后一个孩子上
		for(int j = 0;j<startNo-1;j++)
		{
			first = first.getNext();
			helper = helper.getNext();
		}
		//当小孩报数时,让first和helper指针同时的移动countNum-1次(因为第一个孩子本身要报数所以间隔为countNum-1),然后出圈
		//这是一个循环操作,直到圈中只有一个节点
		while(true)
		{
			if(helper == first)
			{
				break;//说明圈中只有一个节点
			}
			//让first和helper指针同时移动countNum-1
			for(int j=0;j<countNum-1;j++)
			{
				first = first.getNext();
				helper = helper.getNext();
			}
			//这时first指向的节点,就是要出圈的小孩节点
			System.out.println("小孩"+first.getNo()+"出圈");
			//这时将first指向的小孩节点出圈
			first = first.getNext();
			helper.setNext(first);
		}
		System.out.println("最后留在圈中的小孩编号是:"+first.getNo());
	}
}
public class Josephu {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner scanner = new Scanner(System.in);
		System.out.println("请输入一开始的孩子数:");
		int a = scanner.nextInt();
		System.out.println("请输入从第几个孩子开始报数:");
		int b = scanner.nextInt();
		System.out.println("请设置数几下后出一个人:");
		int c =scanner.nextInt();
		CircleSingleLinkedList list = new CircleSingleLinkedList();
		list.addBoy(a);
		System.out.println("原有编号顺序:");
		list.showBoy();
		System.out.println("出圈顺序:");
		list.countBoy(b, c, a);	
	}
}

测试结果:

请输入一开始的孩子数:
5
请输入从第几个孩子开始报数:
1
请设置数几下后出一个人:
2
原有编号顺序:
小孩的编号:1
小孩的编号:2
小孩的编号:3
小孩的编号:4
小孩的编号:5
出圈顺序:
小孩2出圈
小孩4出圈
小孩1出圈
小孩5出圈
最后留在圈中的小孩编号是:3

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值