概述
约瑟夫问题是什么:约瑟夫问题
简单的来讲,约瑟夫问题就是一个拥有n个人围成的圆圈,从第m个人开始报数k,第k个报数的人出圈,直到剩下最后一个人。
解决约瑟夫问题通常有两种算法
- 数组取模
- 单向环形链表
由于数组取模在数组模拟队列中已经演示过,本次只做单向环形链表的实现
单向环形链表
节点对象:
package com.leolee.dataStructure.linkedList;
/**
* @ClassName JosephNode
* @Description: 约瑟夫问题节点
* @Author LeoLee
* @Date 2020/9/15
* @Version V1.0
**/
public class JosephNode {
private int no;
private JosephNode next;//下一个节点,默认为空
public JosephNode (int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public JosephNode getNext() {
return next;
}
public void setNext(JosephNode next) {
this.next = next;
}
}
单项环形链表实现:
package com.leolee.dataStructure.linkedList;
/**
* @ClassName CircleSingleLinkedList
* @Description: 环形单向链表
* 约瑟夫问题,n个小孩围成一圈,从第m个小孩开始报数k,第k个报数的小孩离开,得出离开的顺序。用单向环形链表解决
* @Author LeoLee
* @Date 2020/9/15
* @Version V1.0
**/
public class CircleSingleLinkedList {
//创建第一个节点
private JosephNode firstNode = null;
//创建有num个节点的单向环形链表
public void create (int num) {
if (num < 1) {
System.out.println("num值不合法");
return;
}
JosephNode currentNode = null;
for (int i = 1; i <= num; i++) {
//根据编号创建节点
JosephNode node = new JosephNode(i);
//添加节点
if (i == 1) {
firstNode = node;
firstNode.setNext(firstNode);//构成只有一个节点的环状
currentNode = firstNode;//让currentNode指向第一个节点
} else {
currentNode.setNext(node);
node.setNext(firstNode);
currentNode = node;
}
}
}
public void showNodes () {
if (firstNode == null) {
System.out.println("当前链表为空");
return;
}
JosephNode currentNode = firstNode;
while (true) {
System.out.printf("当前节点编号:[%d]\n", currentNode.getNo());
if (currentNode.getNext() == firstNode) {//遍历完毕
break;
}
currentNode = currentNode.getNext();//currentNode后移到下一个节点
}
}
/**
* 功能描述: <br> 用单向环形链表解决约瑟夫问题(也可以用数组取模)
* 〈〉
* @Param:
* [startNo第几个小孩开始,
* countNum报数次数,
* num一共多少个小孩]
* @Return: void
* @Author: LeoLee
* @Date: 2020/9/15 13:22
*/
public void countBoy (int startNo, int countNum, int num) {
if (firstNode == null || startNo < 1 || countNum < 1 || num < 1 || startNo > num) {
System.out.println("当前链表不合法");
return;
}
//报数前,先让tempNode和firstNode移动(k - 1)次,相当于firstNode在报数小孩位置,tempNode在firstNode前
for (int i = 0; i < startNo - 1; i++) {
firstNode = firstNode.getNext();
}
//创建一个辅助“指针”,指向链表的最后一个节点(firstNode的上一个节点)
JosephNode tempNode = firstNode;
while (true) {
if (tempNode.getNext() == firstNode) {
break;
}
tempNode = tempNode.getNext();
}
//开始按照规定,把小孩踢出环形链表,直到环形链表中只有一个小孩(节点)
while (true) {
if (tempNode == firstNode) {//只有一个节点时,退出循环
break;
}
//将firstNode和tempNode移动 countNum -1 次
for (int i = 0; i < countNum - 1; i++) {
firstNode = firstNode.getNext();
tempNode = tempNode.getNext();
}
//当firstNode和tempNode移动完成后,fisrtNode指向的节点(小孩),就是要出圈的节点
System.out.printf("节点[%d]出圈\n", firstNode.getNo());
firstNode = firstNode.getNext();
tempNode.setNext(firstNode);
}
System.out.printf("最后留在圈中的节点:[%d]\n", firstNode.getNo());
}
}
测试方法:
public static void main(String[] args) {
//单向环形链表
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.create(5);
circleSingleLinkedList.showNodes();
//出圈测试
circleSingleLinkedList.countBoy(1, 2, 5);
}
输出如下:
当前节点编号:[1]
当前节点编号:[2]
当前节点编号:[3]
当前节点编号:[4]
当前节点编号:[5]
节点[2]出圈
节点[4]出圈
节点[1]出圈
节点[5]出圈
最后留在圈中的节点:[3]