Josephu 问题为:设编号为 1,2,… n 的 n 个人围坐一圈,约定编号为 k(1<=k<=n)的人从 1 开始报数,数到 m 的那个人出列,它的下一位又从 1 开始报数,数到 m 的那个人又出列,依次类推,直到所有人出列为止,由此 产生一个出队编号的序列。
解题思路
一、先创建一个循环链表
1.先创建first节点,然他先自己成“环”,即first.next = first;
2.然后开始往后面追加,这时候需要一个辅助变量temp,一开始temp=first。
temp.next = newNode;
newNode.next = first;
temp = newNode;
二、开始根据用户输入,确定出圈顺序
1.首先,要定义一个辅助变量helper,helper指向循环链表最后一个节点。这一步用while循环就可以了
2.先让first移动到要开始数数人的位置,当然helper也跟着移动。
比方说下面的例题就是:小孩报数前,先让first和helper移动k-1次(for循环就可实现)
3.开始数数喽,如果数到m就出列,那么first和helper就同时需要移动m-1次
,然后这时候就可以将first指向小孩节点出圈(first节点就是要出圈的小孩),核心代码:
输出小孩编号,然后
first=first,next;
helper.next = first;
4.最后会剩下一个,判断条件
helper = first;
打印输出最后一个即可。
package com.gk.linkedlist;
/**
* 约瑟夫问题:
* Josephu 问题为:设编号为 1,2,… n 的 n 个人围坐一圈,约定编号为 k(1<=k<=n)的人从 1 开始报数,
* 数到 m 的那个人出列,它的下一位又从 1 开始报数,数到 m 的那个人又出列,依次类推,直到所有人出列为止,
* 由此 产生一个出队编号的序列。
*
* 根据用户的输入,生成一个小孩的出圈顺序
* n=5,即有5个人
* k=1,从第一个人开始报数
* m=2,数2下
*
* 1.需求创建一个辅助指针(变量)helper,事先应该指向环形链表的最后这个节点
* 2.小孩报数前,先让first和helper移动k-1次
* 3.当小孩报数时,让first和helper指针同时的移动m-1次
* 4.这时候就可以将first指向小孩节点出圈
* first = first.next;
* helper.next = first;
* 原来first指向的节点就没有任何引用,就会被回收
*
* @author gk
*
*/
public class Josepfu {
public static void main(String[] args) {
CircleSingleLinkedList cs = new CircleSingleLinkedList();
cs.add(5);
cs.showBoy();
cs.countBoy(1, 2, 5);//出圈顺序2-4-1-5-3
}
}
//创建一个循环链表
class CircleSingleLinkedList{
//创建一个first节点,当前没有编号
private Boy first = null;
//添加节点,构成一个单向循环链表
public void add(int nums) {
//先做数据校验
if(nums < 1) {
System.out.println("nums的值不合法!");
return;
}
Boy curBoy = null;//辅助指针,帮助我们构建环形链表
//开始构建链表
for(int i = 1; i <= nums; i++) {
//根据编号创建小孩节点
Boy boy = new Boy(i);
if(i == 1) {
first = boy;
first.setNext(first);
curBoy = first;
} else {
curBoy.setNext(boy);
boy.setNext(first);
curBoy = boy;
}
}
}
//遍历当前的循环链表
public void showBoy() {
//判断链表是否为空
if(first == null) {
System.out.println("没有任何小孩");
return;
}
Boy curBoy = first;
//链表不为空,开始遍历
while(true) {
System.out.printf("小孩的编号为:%d\n", curBoy.getNo());
if(curBoy.getNext() == first) {
break;
}
curBoy = curBoy.getNext();//curBoy后移
}
}
/**
* 根据用户的输入,计算出小孩的出圈顺序
*
* @param startNo 表示从第几个小孩开始数数
* @param countNum 表示数几下
* @param nums 表示最初由几个小孩
*/
public void countBoy(int startNo, int countNum, int nums) {
//先对数据进行校验
if(first == null || startNo < 1 || startNo > nums) {
System.out.println("输入的参数有误,请重新开始");
return;
}
//创建要给辅助指针,帮助完成小孩出圈
Boy helper = first;
//1.需要创建一个辅助指针(变量)helper,事先应该指向环形链表的最后一个节点
while(true) {
if(helper.getNext() == first) {//说明helper已经指向最后一个节点了
break;
}
helper = helper.getNext();
}
//2.小孩报数前先让first和helper同时移动startNo-1次 (因为要从startNo开始报数)
for(int i = 1; i <= startNo-1; i++) {
first = first.getNext();
helper = helper.getNext();
}
//3.游戏开始,小孩开始报数时,first和helper同时移动countNum-1次
//这是一个循环操作,直到圈中只有一个小孩
while(true) {
if(helper == first) {
break;
}
for(int j = 1; j <= countNum-1; j++) {
first = first.getNext();
helper = helper.getNext();
}
//4.数出一个人了,开始出圈
System.out.printf("%d号孩子开始出圈\n",first.getNo());
first = first.getNext();
helper.setNext(first);
}
System.out.printf("最后出圈的小孩为%d",first.getNo());
}
}
//创建一个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;
}
}