约瑟夫问题
约瑟夫问题有时也称为约瑟夫斯置换,类似问题又称为约瑟夫环。又称“丢手绢问题”.
问题由来
据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
问题转换:
41个人坐一圈,第一个人编号为1,第二个人编号为2,第n个人编号为n。
1、编号为1的人开始从1报数,依次向后,报数为3的那个人退出圈;
2、自退出那个人开始的下一个人再次从1开始报数,以此类推;
3、求出最后退出的那个人的编号。
提示:
用一个循环链表来处理Josephu问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开’始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。
解决问题:
1、构建含有41个结点的单向循环链表,分别存储1~41的值,分别代表这41个人;
2、使用计数器count,记录当前报数的值;
3、遍历链表,每循环一次,count++;
4、判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0;
代码:
1、节点类
public class Node<T>{
//存储数据
T element;
//下一个节点
Node next;
public Node(T element, Node next) {
this.element = element;
this.next = next;
}
}
2、测试代码
public class JosephTest {
public static void main(String[] args) {
//1、构建循环链表,包含41个节点,分别存储1~41之间的值
//记录首节点
Node<Integer> first = null;
//记录下一个节点
Node<Integer> down = null;
for (int i = 1; i <=41 ; i++) {
//如果是第一个节点
if(i==1){
first = new Node<>(i,null);
down = first;
continue;
}
//如果不是第一个节点
Node<Integer> newNode = new Node<>(i, null);
down.next = newNode;
down = newNode;
//如果是最后一个节点,让最后一个节点的下一个节点为frist
if(i==41){
down.next=first;
}
}
//2、定义一个计数器,模拟报数
int count = 0;
//3、遍历循环链表
//记录每次遍历拿到的节点,第一次是首节点
Node<Integer> now = first;
//当前节点的上一个节点
Node<Integer> before = null;
//如果不是只有一个节点就继续遍历
while (now!=now.next){
//模拟报数
count++;
//判断当前节点是不是3
if(count==3){
//如果是3,移除当前节点,并打印,重置计数器,让当前节点后移
before.next = now.next;
System.out.print(now.element+",");
count = 0;
now = now.next;
}else {
//如果不是3,让before为当前节点,并让当前节点下移
before = now;
now = now.next;
}
}
//打印最后一个元素
System.out.println(now.element);
}
}
3、测试结果:
最后两个数为:16,31。
约瑟夫要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。