递归实现Josephus问题

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

猜你喜欢

转载自blog.csdn.net/szlg510027010/article/details/82952134
今日推荐