约瑟夫问题
约瑟夫问题有时也称为约瑟夫斯置换或者猴子选大王问题,是一个出现在计算机科学和数学中的问题。
在计算机编程的算法中,类似问题又称为约瑟夫环。又称"丢手绢问题"。
约瑟夫环问题一般形式
约瑟夫问题是个有名的问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。例如N=6,M=5,被杀掉的顺序是:5,4,6,2,3,1。
本文中的约瑟夫问题
假设有nums个节点,从第startNo个开始报数,输出第countNum个节点,并将第countNum个节点从nums个节点中删除,请问输出节点的顺序。
代码实现
/**
* 创建一个Node类
*/
class Node {
private int no;// 编号
private Node next; // 指向下一个节点
public Node(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
/*
* 创建一个环形的单向链表
*/
class JosephUtil {
// 创建一个first节点,始终指向第一个节点
private Node first = null;
private int nums;
/*
* 构建成一个环形的链表
*/
public JosephUtil(int nums) {
this.nums = nums;
if (nums < 1) {
System.out.println("节点数量不正确");
return;
}
Node curNode = null; // 辅助指针,帮助构建环形链表
// 使用for来创建我们的环形链表
for (int i = 1; i <= nums; i++) {
// 根据编号,创建节点
Node node = new Node(i);
// 如果是第一个节点
if (i == 1) {
first = node;
first.setNext(first); // 构成环
curNode = first; // 让curNode指向第一个节点
} else {
curNode.setNext(node);
node.setNext(first);
curNode = node;
}
}
}
/*
* 遍历当前的环形链表
*/
public void showNode() {
// 判断链表是否为空
if (first == null) {
System.out.println("没有节点");
return;
}
// first始终指向第一个节点,需要使用一个辅助指针完成遍历
Node curNode = first;
while (true) {
System.out.printf("节点 %d \n", curNode.getNo());
if (curNode.getNext() == first) {
break;
}
curNode = curNode.getNext();
}
}
/**
* 根据输入的内容,计算出输出的顺序
*
* @param startNo 表示从第几个节点开始数
* @param countNum 表示第几个开始输出
* @param nums 表示在环形链表中有多少个节点
*/
public void josephPrint(int startNo, int countNum) {
// 先对数据进行校验
if (first == null || startNo < 1 || startNo > nums) {
System.out.println("参数输入有误, 请重新输入");
return;
}
// 输出节点前,先让first移动k-2次
for (int j = 0; j < (startNo + nums - 2) % nums; j++) {
first = first.getNext();
}
// 创建辅助指针,指向first节点的后面一个节点
Node helper = first;
first = helper.getNext();
// 当输出节点时,让first和helper指针同时移动m-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.printf("节点%d输出\n", first.getNo());
// 将first指向的节点从环形链表中删除
first = first.getNext();
helper.setNext(first);
}
System.out.printf("最后一个节点编号%d \n", first.getNo());
}
}
/**
* 测试
*/
public class Joseph {
public static void main(String[] args) {
// 构建环形链表
JosephUtil josephUtil = new JosephUtil(5);
josephUtil.showNode();
// 测试打印节点是否正确
josephUtil.josephPrint(1, 2); // 2->4->1->5->3
}
}