Java实现Josephus约瑟夫环问题的算法
前言
- 语言:Java
- 环境:IntelliJ IDEA
- JDK版本:1.8
- 源码:GitHub
问题概述
N个人围成一圈,规定报数为M,第一个人从1开始报数,报数到M的人将被杀掉,再从下个人继续从1报数,如此循环,最后剩下一个,其余人都将被杀掉。求被杀人的顺序和最后幸存者的编号
基于链表的解法
用链表求解,我们首先要想到的是,这个问题是循环报数,因此我们应该选择循环链表,由于报数只需要朝着同一方向报数,所以不用考虑双向循环链表。
确定了用循环链表,那么这个链表上的每个节点就保存的是参与者信息,例如编号。我们规定参与者个数为size
,报数为k
的人死亡,那么,我么需要考虑下列问题:
- 如何完成报数:使用一个
num
变量计数,当为k
时重置num
- 如何删除死亡人员:单向链表删除一个节点需要一个辅助指针,并指向要删除节点的前一个节点,我们定义为
pre
指针 - 如何确定人员死亡后,从下个成员重新报数:我们定义为
target
指针,指向报数人员
流程为:先创建循环链表,该链表的节点保存参与人员编号,节点个数为size
,创建过程中让pre
指针始终指向刚创建的节点,这样创建完成后pre
就指向最后一个节点,让target
指向链表创建的第一个结点,保持pre
始终指向target
的前一个节点。流程开始,num
计数,当num
为k
时,通过pre
删除死亡人员节点,target
指向死亡人员节点的下一个节点。重复此过程,直到节点删除剩余一个,最后这个节点,pre
和target
都指向他,即该节点的next也指向自己,游戏结束。
/**
* 基于链表的实现
* @param size 参与人数
* @param k 报号
*/
public static void josephousLinked(int size,int k){
Node first = null; //指向编号为0的人的指针
Node pre = null; //指向即将死亡人员前一名人员的指针
Node target = null; //指向即将死亡人员的指针
int num = 0; //计数器,0~k
if(size>0){ //初始化第一个人员
first = new Node(0);
pre = first;
}
for(int i = 1;i
基于数组的解法
使用数组求解,先构建一个大小为参与人数size
大小的数组,我们用数组索引表示这些人的编号。这个数组中存放的是每个人的状态,由于数组刚构建时为0,因此我们规定,数组值为0,则这个人存活,若为1,则这个人死亡,报数为k
的人死亡,直到数组中只有一个存活的人,且这个人索引对应的数组值为0,则游戏结束。我们需要考虑以下问题:
- 如何完成报数:使用一个
num
变量计数,当为k
时重置num
- 如何删除死亡人员:将这个人员索引对应的数组值置为1,且遍历到数组值为1的人,只增加索引,不增加计数器
num
- 如何确定人员死亡后,从下个成员重新报数:死亡人员索引+1,需要考虑到数组越界,因此需要取模
/**
* 基于数组的实现
* @param size 参与人数
* @param k 报号
*/
public static void josephousArray(int size,int k){
int num = 0; //计数器,0~k
int index = 0; //游标
int sur = size; //存活人数
int[] array = new int[size]; //所有人组成的数组
while(true){
if(array[index%size] == 1){ //如果这个人已经死亡,则下一位开始
index++;
continue;
}
num++;//当前人员报数
if(num==k){ //如果报数到k时,这个人员死亡
array[index%size] = 1; //标记死亡
System.out.println("第"+index%size+"号人死亡"); //打印信息
num = 0; //重置计数器
sur--; //存活人员减少
}
if(sur==1&&array[index%size]==0){ //到最后一人时停止报数,游戏结束
break;
}
index++;
}
System.out.println("幸存者为"+index%size+"号");
}
基于递归的解法
在使用递归之前,我们先要找到约瑟夫环中的规律,如果有下面的数组:
0,1,2,3,4
报数为3的人死亡,则第一个死亡的人一定是2号,也就是说,当给定一个人员总数size
,和报数k
,那么死一个死亡人员一定是(k-1)%size
,这里考虑到报的数k-1可能会大于size,所以要取模。当2号死亡后,我们重新排列数组:
3,4,0,1
我们对这个新数组重新排号为
0,1,2,3
我们可以理解为,要求出0,1,2,3这个数组的最后死亡的人(也就是幸存者),通过一定的对应关系,就能推导出3,4,0,1的幸存者,那么要求出0,1,2,3这个数组的最后死亡的人,我们又可以类比为第一次的做法,重新排序,只需要求出0,1,2的最后死亡的人,直到类比到求出数组只有一个人员0的最后一个死亡的人,显而易见,数组只有0,则不管报什么号,最后一个死亡的都是他,也可以说幸存者就是他。我们先求出简单解:
F(1) = 0 (size=5,k=3)
F(2) = 1 (size=5,k=3)
F(2) =( F(1)+k)%size
我们便得到了递推公式:F(size) = (F(size-1)+k)%size
,例如:要求出第n个人的第n个死亡人员编号,只需要知道第n-1个人第n-1个死亡人员的编号
/**
* 基于递归的实现
* @param size 参与人数
* @param k 报号
*/
public static void josephousRecursion(int size,int k){
for(int i = 1;i