算法(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单向环形链表示意图:

算法(Java实现)-图解Josephu(约瑟夫)问题_第1张图片

假设:

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

预期结果:

算法(Java实现)-图解Josephu(约瑟夫)问题_第2张图片

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

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

算法(Java实现)-图解Josephu(约瑟夫)问题_第3张图片

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

算法(Java实现)-图解Josephu(约瑟夫)问题_第4张图片

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

算法(Java实现)-图解Josephu(约瑟夫)问题_第5张图片

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

算法(Java实现)-图解Josephu(约瑟夫)问题_第6张图片

2.3遍历环形链表

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

2.4出圈思路分析

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

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

算法(Java实现)-图解Josephu(约瑟夫)问题_第7张图片

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

算法(Java实现)-图解Josephu(约瑟夫)问题_第8张图片

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

算法(Java实现)-图解Josephu(约瑟夫)问题_第9张图片

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

测试结果:

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

 

你可能感兴趣的:(算法(Java),算法,数据结构,java,单链表,链表)