约瑟夫环问题:一圈共有N个人,开始报数,报到M的人自杀,然后重新开始报数,问最后自杀的人是谁?
如图:内环表示人排列的环,外环表示自杀顺序;上面N=41,M=3。
一.很容易想的是:建立一个动态数组,把1-n的编号放里面,每死一个人移除一个人,在记录下当前位置,递归到数组长度为1,就是活着的那个人了。
下面的是代码:
import java.util.ArrayList; import java.util.Scanner; public class 约瑟夫环 { public static ArrayList<Integer>arrayList; static{ arrayList = new ArrayList<Integer>(); } static void init(int n){ arrayList.clear(); for(int i = 1; i <= n; i++){ arrayList.add(i); } } static int work(int ele, int pos){ if(arrayList.size() == 1) return arrayList.get(0); else{ int cur = (pos + ele - 1)%arrayList.size(); if(cur == 0) cur = arrayList.size(); System.out.printf("%d死了 ", arrayList.get(cur - 1)); arrayList.remove(cur - 1); //System.out.println(arrayList.size()); return work(ele, cur%arrayList.size() == 0? arrayList.size() : cur%arrayList.size()); } } public static void main(String[] args) { Scanner cin = new Scanner(System.in); int m, n; while(cin.hasNext()){ n = cin.nextInt(); m = cin.nextInt(); init(n); System.out.printf("最后%d活着 ", work(m, 1)); } } }
二.其实还有一种递推的解法。
为了方便,我们在这里把这n个人的编号从0开始。那么我们去掉的人总是m%n-1,剩下n-1个人,这n-1个人又是一个从第m%n开始的新的约瑟夫环问题。
这个写法初一看不好理解,那么我们把编号转变下:
k –> 0
k+1 –> 1
k+2 –> 2
…
…
k-2 –> n-2
k-1 –> n-1
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根
据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x=(x+k)%n
如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的
情况 —- 这显然就是一个倒推问题!
递推公式为:
F(1,m) = 0;
F(n,m) = (m%n + F(n-1, m))%n; (n>= 2)
很容易得到代码:
cin>>N; cin>>M; int result=0;//N=1情况 for (int i=2; i<=N; i++) { result=(result+M)%i; } cout<<"最后自杀的人是:"<<result+1<<endl;