约瑟夫问题
设编号为1,2,…,n的n个人围在一圈,约定编号为k(1≤k≤n)的人从1开始报数,数到m的那个人出列,出列的人的下一位又开始从1开始报数,数到m的那个人又出列,以此类推,直到所有人出列为止,产生一个出队编号的序列
假设n=5,即有5个人围在一圈;k=1,即从第一个人开始报数;m=2,即数两下,则最后出队编号的序列为24153
思路:可以用一个不带头结点的循环链表来处理,先构成一个有n个结点的单循环链表(单向环形链表),然后由k结点从1开始计数,计到m时,对应的结点从链表中删除,然后再从被删除结点的下一个结点又开始从1计数,直到最后一个结点从链表中删除,算法结束
构建第一个单向环形链表的思路
- 先创建first头节点,创建第一个节点之后,使first头节点指向该节点,并使此第一个节点的next指向头节点,从而形成环形链表
- 创建第二个节点,使第一个节点的next指向第二个节点(这一步需要使用辅助指针temp),第二个节点的next指向first头节点即可。以此类推
遍历环形链表
- 创建一个辅助指针temp,指向first节点
- 通过while循环遍历该环形链表即可,要注意遍历结束的条件,temp.next == first即为遍历结束
设环形链表的大小为n,编号为k(1≤k≤n)的节点从1开始数,数到m的节点出链表,生成出链表的顺序
- 需要一个辅助指针(变量)temp,事先应该指向环形链表的最后的节点;first指针指向头节点
- 计数前,使first和temp指针移动k-1次,first指针移动到指向k节点,temp指针移动到指向k节点前一个节点
- 当给定m,开始计数的时候,first和temp指针同时移动m-1次,因为从k节点开始数的时候,k节点是数的第一下,比如m=2时,first指向k+1,temp指向k
- 此时first指针指向的节点k+1出链表
出链表的条件为:first先后移,first = first.next
temp的next指针指向first,temp.next =first
k+1节点没有任何的引用,就会被JVM垃圾回收机制回收
约瑟夫问题的实现代码
直接拷贝至你们的开发工具就能运行,代码上我写了详细的注释,思路应该比较清晰
代码实际上就是上述实现思路的翻译
package com.test.chap2;
public class Josephu {
public static void main(String[] args) {
CircleSingleLinkedList csl = new CircleSingleLinkedList();
csl.addNode(5);//加入5个节点
csl.showNode();
//测试约瑟夫问题
csl.countNode(5, 1, 2);
}
}
//创建一个环形的单向链表
class CircleSingleLinkedList {
// 创建一个first头节点,当前没有编号
private Node first = null;
// 添加节点,构成一个环形链表
public void addNode(int numbers) {// numbers代表整个环形链表一共有多少个节点
// numbers的数据校验
if (numbers < 1) {
System.out.println("numbers的值错误");
return;
}
// 创建辅助指针temp,帮助构建链表
Node temp = null;
// for循环来创建环形链表
for (int i = 1; i <= numbers; i++) {
// 根据编号,创建节点
Node node = new Node(i);
// 如果是第一个节点
if (i == 1) {
first = node;
first.setNext(first);// 构成环形
temp = first;// 使temp指向第一个节点,方便后续添加节点
} else {
temp.setNext(node);// 使temp的next指向新节点
node.setNext(first);// 新节点的next指向first节点
temp = node;// 临时指针temp后移,指向新节点
}
}
}
// 遍历当前的环形链表
public void showNode() {
// 判断链表是否为空
if (first == null) {
System.out.println("链表为空");
return;
}
// 因为first是固定的,所以需要一个辅助指针temp完成遍历
Node node = first;
while (true) {
System.out.printf("节点的编号为:%d\n", node.getNumber());
//如果node的next节点为first头节点,说明链表已经遍历完了,直接退出
if (node.getNext() == first) {
break;
}
node = node.getNext();// 使temp后移
}
}
/*
* 根据用户的输入,numbers指定链表大小 k指定从哪个节点开始 m指定计数的大小 计算出节点出链表的顺序
*/
public void countNode(int numbers, int k, int m) {
// 先对链表进行校验
if (first == null || k < 1 || k > numbers) {
System.out.println("参数输入有误!请重新输入");
return;
}
// 创建辅助指针temp,帮助完成节点出链表
Node temp = first;
//辅助指针temp,事先应该指向环形链表最后的节点
while(true) {
if(temp.getNext() == first) {//temp的next是first头节点,说明temp指向了最后的节点
break;
}
temp = temp.getNext();//如果if条件没有满足,temp后移,直到满足if条件
}
//退出while循环之后,temp就了指向最后的节点
//计数前,first和temp指针移动k-1次
for(int i = 0; i < k - 1; i++) {
first = first.getNext();
temp = temp.getNext();
}
/*
* 给定m值,开始计数,first和temp指针同时移动m-1次
* first指向的节点即为待删除节点
* 需要循环操作来删除节点,直到链表中只剩一个数据
*/
while(true) {
if(temp == first) {//说明链表中只有一个节点
break;
}
//first和temp指针同时移动m-1次
for(int i = 0; i < m - 1; i++) {
first = first.getNext();
temp = temp.getNext();
}
//此时first指向的节点就是要出链表的节点
System.out.printf("节点%d出链表\n", first.getNumber());
//将first指向的节点出链表
first = first.getNext();//first指针后移
/*
* 因为next是私有属性,不能使用temp.getNext() = first;来把temp的next域指向first
* 需要使用temp.setNext(first);
*/
temp.setNext(first);
}
//循环结束,链表中只剩一个数据
System.out.printf("最后留在链表中的节点的编号为:%d\n", first.getNumber());
}
}
//创建Node类,表示一个节点,节点的信息只需要一个number
class Node {
private int number;// 编号
/*
* 指向下一个节点next,默认为null,注意是私有变量,通过getter和setter来对值进行操作
* next能够指向下一个节点,因为addNode()方法给next赋予指向下一个节点的逻辑
*/
private Node next;
public Node(int number) {
this.number = number;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}