约瑟夫环详解

约瑟夫环问题:一圈共有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;

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注