一、什么是约瑟夫问题
百度百科如是说:据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
二、约瑟夫问题程序设计思路
首先,我们想到的是环的问题,而单项环形链表则正好可以作为这个问题的模型载体,从节点1开始,依次循环每个节点,当节点为3时,将这个节点删除,怎么删除呢,只需要将上一个节点指向这个节点的下一个节点即可,然后再从下一个节点开始从1数,到3又执行删除操作,直到最后只剩下一个节点,就是最后能存活下来的人,我们每次删除节点,就打印一下被删除的节点元素,这样就知道依次被删除的节点了,于是问题转化如下图:
二、Java代码实现
package mypackage;
//测试
public class MyJava {
//节点类
private static class Node<T> {
T data;
Node next;
//构造方法
public Node(T data, Node next) {
this.data = data;
this.next = next;
}
}
public static void main(String[] args) {
// 创建一个含有41个节点的环形链表
// 记录首节点和某个节点的前一个节点
Node<Integer> first=null;
Node<Integer> pre=null;
// 特别要理解怎么创建的循环链表
for (int i=1;i<=41;i++) {
// 第一次循环,创建第一个节点
// 此时pre=first
if (i==1){
first=new Node<>(i,null);
pre=first;
}
// 如果不是第一个节点
// 先创建一个新节点,让上一个节点指向新节点,且让这个新节点成为pre,用于下一次循环的上一个节点
// 并且需要判断,如果此次循环的i=41,则让这个节点指向first,形成环
else {
Node newnode=new Node(i,null);
pre.next=newnode;
pre=newnode;
// 如果是最后一个节点
if(i==41) {
pre.next=first;
// 也可以是newnode.next=first;
}
}
}
// 开始约瑟夫问题的操作
// 先定义一个计数器,用于后续循环记录报数
int count=0;
// 当前节点和前一个节点
Node curnode=first;
Node<Integer> beforenode=null;
System.out.println("依次应该自杀的节点如下:");
// 开始循环,循环结束的标志是自循环,即只剩下一个节点
while (curnode!=curnode.next){
// 计数,相当于第几个报数的,注意是first是第一个报数的
count++;
// 如果不是第三个报数的,让beforenode=curnode,然后再让curnode后移一个节点
if (count!=3){
beforenode=curnode;
curnode=curnode.next;
}
// 如果是第三个报数的,让beforenode的指向curnode的下一个节点
// 并且打印出curnode,归零count从新计数,且让curnode后移一个节点
else {
beforenode.next= curnode.next;
System.out.print(curnode.data+",");
count=0;
curnode=curnode.next;
}
}
// 最后一个元素
System.out.print(curnode.data);
}
}
结果: