1、Josephu(约瑟夫)
问题描述:设编号为1,2,3,,,,n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列,和最后剩下的那个人。求这个序列和最后那个人的编号。
整体解题思路:用一个不带头节点的单向环形链表来处理该问题。
- 先构成一个有n个结点的单向环形链表
- 由k结点起从1开始计数,计到m时,对应结点从链表中删除
- 被删除的下一个节点又从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遍历环形链表
- 先让一个辅助变量curBoy,指向first节点
- 然后通过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