Josephus问题是古代一个著名的数学难题。围绕这个问题有很多故事。 其种一个说Josephus是一群被罗马人抓获的犹太人中的一个,为了不被奴役,他们选择了自杀。 他们排成一个圆圈,从某个人开始,沿着圆圈计数。每报第n个数的人就要离开圆圈去自杀。但是Josephus不想死,他想活下来,所以他制定了规则,以使他称为最后一个离开圆圈的人(最后一个离开表示其他人都出圈去自杀了,他就活下来了)。 如果有(例如)有5个人,标号1 2 3 4 5,从2开始数,数3个就第3个出去自杀,2 3 4,故标号4的人出去自杀,剩下1 2 3 5,又从5开始数,5 1 2,故标号2的人出去自杀剩下1 3 5,又从3开始数,3 5 1,故1出去自杀,然后从标号为1的下一个标号3开始数:3 5 3所以标号为3的出去自杀,这时候只剩下标号5了,也就是说除了标号5其他人都自杀go die了,Josephus很快想到了这个点子,占据了5号位置,活了下来。
-
第一种方法:
最简单粗暴的方法就是模拟该过程,用一个循环链表即可实现,比较容易实现。制造一个链表,将编号1定为first结点,然后从上往下开始一直插入结点,因为编号是按顺序的,所以可用一个for循环来实现。完成赋值工作以后,紧接着只需要遍历链表即可,当达到对应的个数时删除该结点。只要该结点可它的next域指向的结点相同,则说明已经是最后一个,不再循环,输出该值。代码中的
n代表:该环的总人数
m代表:对应的编号应该去自杀了
k代表:开始游戏的起始编号
首先定义一个结点类:
/**
* 定义Josephus结点类
* @author Administrator
*
*/
public class JosephusLink {
public int data;
public JosephusLink next;
public JosephusLink(int data) {
super();
this.data = data;
this.next = null;
}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
*
* @author Administrator
*
*/
public class Josephus {
private JosephusLink first; //定义first结点
private JosephusLink newLink; //方便插入结点
public Josephus(int n,int m,int k){
first = new JosephusLink(1); //初始化first结点
newLink = first;
//共有n人
for(int i=2;i<=n;i++) { //开始赋值工作
newLink.next = new JosephusLink(i);
newLink = newLink.next;
}
//循链表
newLink.next = first;
//从第m个人开始
for(int i=1;i<k;i++) {
newLink = newLink.next;
}
while(newLink!=newLink.next) { //遍历链表,只要不是同个结点就往下遍历
for(int i=1;i<m;i++) {
newLink = newLink.next;
}
newLink.next = newLink.next.next; //对应编号go die
}
System.out.println("The lastest man's number is: "+newLink.data);
}
//getString()方法 和 getInt()方法均为了输出,当做上次迭代器的练习,可以忽略
public static String getString() throws IOException{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
return s;
}
//可忽略
public static int getInt() throws IOException{
String s = getString();
return Integer.parseInt(s);
}
//测试
public static void main(String[] args) throws IOException {
int n,m,k;
System.out.println("Please enter numbers n: ");
n = getInt();
System.out.println("Please enter numbers m: ");
m = getInt();
System.out.println("Please enter numbers k: ");
k = getInt();
System.out.println(" ");
Josephus o = new Josephus(n,m,k);
}
}
时间复杂度:O(N*M),这耗时太长,我们下面将其进行优化。
-
第二种方法:
递归法,运用数学上巧妙的方式将其进行整理,优化。在该优化中,为了更好的理解其思想,我们将k略去,默认其每一个Josephus环都是从1开始的,第m个人出去自杀。
为了让大家方便理解,在这里举了个例子并用了一个表格呈现:(例子为一共有7个人即n=7,第3个人go die即m=3)
假设我们已经知道最终能够活下来的人的编号为4,我们找一找4在n==7的时候的位置,它在小标为3的地方,当n==6的时候,它在下标为0的地方,当n==5的时候,它又在下标为3的地方了,以此类推。嗯,说这么多看不出来有什么东西?我们尝试逆着看回去,当n==5,它在下标为3的地方,n==6的时候,它在下标为0的地方,n==7的时候,它在下标为3的地方,我们可以发现n==7时候的下标是n==6所处下标即0+3然后再余%7得来的,当n==6的时候,我们发现是n==5所处下标即3+3然后%6得来的,往后以此类推。
于是呢,我们就可以得到这么一条规则:幸存者在该轮所处的位置都是上一轮加上步长m后余该轮总人数得来的。所以我们可以从第二轮,一层层往上遍历,当总人数为n的时候,我们就知道编号是多少了。因为我们都是按顺序来的,编号总是比下标大1,所以我们找到编号所处的位置即下标我们就找到了该编号。
(问:为什么从0开始,因为求余得到的数有可能是0。)
代码:
import java.util.Scanner;
public class TestJ {
public static void main(String[] args){
System.out.println("Please enter n and m: ");
Scanner s = new Scanner(System.in);
int n = s.nextInt();
int m = s.nextInt();
int number = Josephus(n,m);
System.out.println(number);
}
public static int Josephus(int n,int m) {
int p = 0;
for(int i=2;i<=n;i++) {
p = (p+m)%i;
}
return p+1;
}
}
时间复杂度:O(N). 有什么不懂的可以问哦。
注:其实还有一种更加优化的方法,我们在取余数的时候发现有些是一样的,我们可以进一步优化,这种优化方案属于竞赛的范畴了,在这里不多做解释,有兴趣的可以看下面这篇文章。觉得挺不错:
https://blog.csdn.net/lianai911/article/details/41828263