1.什么是约瑟夫问题
约瑟夫问题(有时也称为约瑟夫斯置换,是一个计算机科学和数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环。又称“丢手绢问题”.)。约瑟夫问题在数据结构中的具体体现为单向循环链表/数组构成的循环队列。
约瑟夫环:
编号为1,2,3......n的n个人围坐在一起,约定编号为k(1≤k≤n)的人从1开始报数,数到m的那个人出列,他的下一位又从1开始报数,数到m的那个人出列,如此循环往复,直到只剩下最后一个winner,由此衍生一个新的链表。
2. 构建一个单向循环链表
public class LoopLinkedList {
private Node node;
//添加节点
public void add(Node newNode) {
//循环链表
//如果起始节点为空,直接赋值给起始节点(并非头节点)
if (node == null) {
node = newNode;
node.next = node;
} else {
Node temp = node;
while (temp.next != node) {
temp = temp.next;
}
newNode.next = node;
temp.next = newNode;
}
}
public void show() {
if (node == null) {
System.out.println("链表为空,遍历失败");
return;
}
Node temp = node;
do {
System.out.println(temp);
temp = temp.next;
} while (temp.next != node.next);
}
}
public class Node {
//编号
private int no;
//数据域
private String data;
//next节点域
private Node next;
public Node(int no, String data, Node next) {
this.no = no;
this.data = data;
this.next = next;
}
@Override
public String toString() {
return "Node{" +
"no=" + no +
", data='" + data + '\'' +
'}';
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
3.约瑟夫问题的出圈
设置一个指针temp,出队时只需找到待出队的前一个节点,使temp.next=temp.next.next,如上图所示,2节点就失去了联系,在JVM-'STOP THE WORLD'的时候,无法回溯到根节点,就会被GC回收。
优化思路:
网上资料及部分书籍用单向循环链表实现时通常的做法是指定3个临时指针来构建 '丢手绢的运行流程'。这里提供一种仅需起始指针的方法进行出队的代码,仅供参考(没有新建一个出队链表,可自行添加)!
仅剩两个节点时
此段代码应与第二小节的代码段一起放在LoopLinkedList中,这里为方便说明,进行了拆分
public class LoopLinkedList {
private Node node;
/**
* @param m 第几个人开始数
* @param n 数几次
*/
public void remove(int m, int n) {
//遍历链表,判断输入参数正确性
Node temp = node;
//获得第一次的起始节点
Node start = null;
int num = 0;
do {
num++;
if (m == num) {
start = temp;
}
temp = temp.next;
} while (temp.next != node.next);
if (m > num) {
System.out.println("参数有误,请重新输入");
return;
}
//移除节点
while (start.next != start) {
//找到待移除的前一个节点
for (int i = 0; i < n - 1; i++) {
start = start.next;
}
if (start.next.next != start) {
start.next = start.next.next;
} else {
//只剩两个节点的时候,移除的是start的下一个节点,而非本身
start.next = start;
}
//从移除节点的下一个节点开始重新计数
start = start.next;
}
System.out.println(start);
}