1. 约瑟夫问题
Josephu问题:设编号为1,2,……,n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列
2. 约瑟夫问题的解决办法
用一个不带头结点的单向环形链表来处理Josephu问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除,则算法结束
3. 单向环形链表代码实现
当n = 5,k = 1,m = 2时,出队列的顺序为2、4、1、5、3
单向环形链表各方法说明:
- add:向单向环形链表添加指定个数节点
- show:遍历显示链表所有节点的数据
- outListShow:解决约瑟夫问题. 打印出小孩出圈的数据
使用单向环形链表解决约瑟夫问题的实现:
public class Josephu {
public static void main(String[] args) {
// 创建单向环形链表
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
// 添加小孩到单向环形链表
circleSingleLinkedList.add(5);
// 查看单向环形链表所有节点数据
circleSingleLinkedList.show();
// 解决约瑟夫问题. 打印出小孩出圈的数据
circleSingleLinkedList.outListShow(1, 2);
}
}
class CircleSingleLinkedList {
// 定义第一个first节点
private Boy firstBoy;
// 记录小孩数量
private int nums;
// 添加指定数量的小孩到单向环形链表
public void add(int nums) {
// 进行nums参数校验
if (nums < 1) {
System.out.println("nums的值不正确");
return;
}
this.nums = nums;
Boy tmpBoy = null;
// 循环添加小孩到单向环形链表
for (int i = 1; i <= nums; i++) {
Boy boy = new Boy(i, "boy" + i);
// 如果是第一个,则进行赋值,然后firstBoy.next指向firstBoy自身
// 再将firstBoy赋值给临时节点
if (i == 1) {
this.firstBoy = boy;
this.firstBoy.next = this.firstBoy;
tmpBoy = this.firstBoy;
// 如果不是第一个,则添加到单向链表的最后,然后将新节点的next指向firstBoy
// 同时将临时节点指向新节点,以便下一次循环处理
} else {
tmpBoy.next = boy;
boy.next = this.firstBoy;
tmpBoy = boy;
}
}
}
// 遍历显示链表的数据
public void show() {
if (this.firstBoy == null) {
System.out.println("没有任何小孩");
return;
}
Boy tmpBoy = this.firstBoy;
while (true) {
System.out.println(tmpBoy.toString());
// 如果当前节点的next,等于firstBoy,则表示遍历完成
if (tmpBoy.next == this.firstBoy) {
break;
// 否则循环处理后面的节点
} else {
tmpBoy = tmpBoy.next;
}
}
}
// 解决约瑟夫问题. 通过传入从第几个小孩开始数,和数几下,打印出小孩出圈的数据
public void outListShow(int startId, int countNum) {
// 进行参数的校验
if (this.firstBoy == null || startId < 1 || startId > this.nums) {
System.out.println("firstBoy为空,或startId输入有误, 请重新输入");
return;
}
// 创建一个帮助节点,用于指向删除节点的上一个节点,用来删除节点
Boy helpBoy = this.firstBoy;
// 循环遍历,让helpBoy指向firstBoy的前一个节点
while (true) {
if (helpBoy.next == this.firstBoy) {
break;
} else {
helpBoy = helpBoy.next;
}
}
Boy currentBoy = this.firstBoy;
// 先向后移动startId -1个节点。其中helpBoy也跟着移动
for (int i = 0; i < startId - 1; i++) {
currentBoy = currentBoy.next;
helpBoy = helpBoy.next;
}
// 然后循环开始计数并依次出圈
while (true) {
// 先移动到要出圈的节点。其中helpBoy也跟着移动
for (int i = 0; i < countNum - 1; i++) {
currentBoy = currentBoy.next;
helpBoy = helpBoy.next;
}
// 进行出圈操作
System.out.println("出圈的小孩数据: " + currentBoy.toString());
// 出圈的节点没有被引用, 会被垃圾回收
currentBoy = currentBoy.next;
helpBoy.next = currentBoy;
// 如果helpBoy等于currentBoy, 则表示只剩最后一个节点了, 进行出圈
if (helpBoy == currentBoy) {
System.out.println("最后出圈的小孩数据: " + currentBoy.toString());
break;
}
}
}
}
// 每个Boy对象就是一个节点
class Boy {
public int id;
public String name;
// 指向下一个节点
public Boy next;
public Boy(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Boy [id = " + id + ", name = " + name + "]";
}
}
运行程序,结果如下:
Boy [id = 1, name = boy1]
Boy [id = 2, name = boy2]
Boy [id = 3, name = boy3]
Boy [id = 4, name = boy4]
Boy [id = 5, name = boy5]
出圈的小孩数据: Boy [id = 2, name = boy2]
出圈的小孩数据: Boy [id = 4, name = boy4]
出圈的小孩数据: Boy [id = 1, name = boy1]
出圈的小孩数据: Boy [id = 5, name = boy5]
最后出圈的小孩数据: Boy [id = 3, name = boy3]