Circular Single Linked List
Josephu(约瑟夫, 约瑟夫环)是一个数学应用问题
- n个人按编号1, 2, …n围坐一圈, 从编号为 k的人开始报数, 数到 m的那个人
出列
, 此时它的下一个人又从1开始报数, 数到 m的那个人再出列, 依次反复, 直到所有的人全部出列为止, 最后输出出列人的编号顺序
解决思路
-
首先创建有 n个节点的
不带头的单向环形链表
, 然后从 k节点开始计数, 计到 m时, 删除对应当前节点, 然后再从被删节点的下一个节点开始重新计数, 依次反复, 直到最后一个节点从链表中删除算法结束 -
示意图
代码示例
/** 定义人(表示节点)*/
class Person {
/** 人编号*/
private int no;
/** 指向下一个节点*/
private Person next;
public Person(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Person getNext() {
return next;
}
public void setNext(Person next) {
this.next = next;
}
}
/** 定义不带头的单向环形链表*/
class CircularSingleLinkedList {
/** 定义起始节点*/
private Person first;
/** 构建 n个节点的链表*/
public void addPersons(int n) {
if (n < 1) {
System.out.println("构建链表失败, 参数错误!");
return;
}
Person current = null;
for (int i = 1; i <= n; i++) {
/** 设定节点编号*/
Person person = new Person(i);
/** 定义起始节点*/
if (i == 1) {
first = person;
/** 起始节点, 指向自己(自己指向自己)*/
first.setNext(first);
/** 保存当前节点, 用于下次循环时配置下一个节点的引用*/
current = first;
} else {
/** 之前的有效环形的指向是 first, 改成当前节点(current的下一个节点)*/
current.setNext(person);
/** 当前节点的下一个节点引用起始节点*/
person.setNext(first);
current = person;
}
}
}
/** 打印所有节点*/
public void print() {
if (first == null) {
System.out.println("链表为空!");
return;
}
Person current = first;
while (true) {
System.out.printf("节点编号 %d \n", current.getNo());
if (current.getNext() == first) {
break;
}
/** 往后移动一个节点*/
current = current.getNext();
}
}
/**
* 计算后打印每个人出列的顺序编号:
* @param startNo: 表示从第几个小孩开始数数
* @param countNum: 表示数几下
* @param nums: 表示最初有多少小孩在圈中
* */
public void countPerson(int startNo, int countNum, int nums) {
if (first == null || startNo < 1 || startNo > nums) {
System.out.println("参数错误!");
return;
}
Person helper = first;
/** 将 helper指向最后节点*/
while (true) {
/** 循环后, 当前节点与起始节点相同, 则循环结束*/
if (helper.getNext() == first) {
break;
}
helper = helper.getNext();
}
/**
* 例: startNo = 1; -1 = 0时不成立跳过!, 如果 startNo大于1就会调整起始节点
* */
for(int i = 0; i < startNo - 1; i++) {
first = first.getNext();
helper = helper.getNext();
}
/** 每次计数时, 将起始节点与最后节点同时向下移动 m - 1次, 然后出列*/
while(true) {
/** 当链表中只有一个节点时结束*/
if(helper == first) {
break;
}
/** 每次循环, 将起始节点与最后节点向下移动: (计数指定次 -1)个节点, */
for(int j = 0; j < countNum - 1; j++) {
first = first.getNext();
helper = helper.getNext();
}
/** 此时起始节点就是要出列的节点*/
System.out.printf("%d号节点出列\n", first.getNo());
/** 将起始使用第二个节点覆盖*/
first = first.getNext();
/** 最后节点的下一个节点(之前的起始节点), 改成目前的起始节点*/
helper.setNext(first);
}
System.out.printf("最后剩余的节点编号 %d \n", first.getNo());
}
}
public class JosephuApp {
public static void main(String[] args) {
/** 创建单向环形链表实例*/
CircularSingleLinkedList linkedList = new CircularSingleLinkedList();
/** 构建5个节点*/
linkedList.addPersons(5);
System.out.println("输出所有节点编号:");
linkedList.print();
System.out.println("计算后打印每个人出列的顺序编号:");
linkedList.countPerson(1, 2, 5);
}
}
输出:
> 输出所有节点编号:
> 节点编号 1
> 节点编号 2
> 节点编号 3
> 节点编号 4
> 节点编号 5
> 计算后打印每个人出列的顺序编号:
> 2号节点出列
> 4号节点出列
> 1号节点出列
> 5号节点出列
> 最后剩余的节点编号 3
如果您觉得有帮助,欢迎点赞哦 ~ 谢谢!!