约瑟夫环问题

问题描述:有n个人,编号分别从0到n-1排列,这n个人围成一圈,现在从编号为0的人开始报数,当报到数字m的人,离开圈子,然后接着下一个人从0开始报数,依次类推,问最后只剩下一个人时,编号是多少?

分析:这就是著名的约瑟夫环问题,关于来历不再说明,这里直接分析解法。

        解法一:蛮力法。我曾将在大一学c语言的时候,用蛮力法实现过,就是采用标记变量的方法即可。

        解法一:循环链表法。从问题的本质入手,既然是围成一个圈,并且要删除节点,显然符合循环链表的数据结构,因此可以采用循环链表实现。

        解法三:递推法。这是一种创新的解法,采用数学建模的方法去做。具体如下:

                   首先定义一个关于n和m的方程f(n,m),表示每次在n个编号0,1,...,n-1中每次删除的报数为m后剩下的数字,

                   在这n个数字中,第一个被删除的数字是(m-1)%n,为了简单,把(m-1)%n记作k,那么删除k之后剩下的数字为0,1,2,...,k-1,k+1,...,n-1

                   并且下一次删除的数字从k+1开始计数,这就相当于剩下的序列中k+1排在最前面,进而形成k+1,..,n-1,0,1,2,...,k-1这样的序列,这个序列最后剩下的数                      字应该和原序列相同,由于我们改变了次序,不能简单的记作f(n-1,m),我们可以记作g(n-1,m),那么就会有f(n,m)=g(n-1,m).

                  下一步,我们把这n-2个数字的序列k+1,..,n-1,0,1,2,...,k-1做一个映射,映射的结果是形成一个从0到n-2的序列。

                           k+1对0,k+2对1,......,n-1对n-k-2,0对n-k-1,1对n-k,....,k-1对n-2

                 这样我们可以把这个映射定义为p,则p(x)=(x-k-1)%n,它表示如果映射前的数字是x,映射后为(x-k-1)%n,从而这个映射的反映射问为p-1(x)=(x+k+1)%n

                 由于映射之后的序列和原始序列具有相同的形式,都是从0开始的序列,所以可以用函数f来表示,即为f(n-1,m),根据映射规则有:

                 g(n-1,m)=p-1[f(n-n,m)]=[f(n-1,m)+k+1]%n,最后把之前的k=(m-1)%n带入式子就会有f(n,m)=g(n-1,m)=[f(n-1,m)+m]%n.

                这样我们就可以得出一个递推公式,

                                                            当n=1时,f(n,m)=0;

                                                            当n>1时,f(n,m)=[f(n-1,m)+m]%n;

                有了这个公式,问题就变得多了。

      由于解法一和解法二,都很好理解,我就不再写具体的代码了,这里我给出解法三Java代码,用递归实现:

 1 import java.util.*;
 2 public class Main{
 3     public static int ysfh(int n,int m){
 4         if(n<=1)return 0;
 5         return (ysfh(n-n,m)+m)%n;
 6     }
 7     public static void main(String[] args) {
 8         // TODO 自动生成的方法存根
 9         Scanner scan=new Scanner(System.in);
10         int n=scan.nextInt();
11         int m=scan.nextInt();
12         System.out.println("最后剩下的编号为:"+ysfh(n,m));
13     }
14 
15 }

测试样例输出为:

10   3
最后剩下的编号为:3

可以看出来,这么复杂的一个问题递归只需要两行代码即可实现,因此在今后的编程的过程中,要灵活运用数学知识。

                   

原文地址:https://www.cnblogs.com/guozhenqiang/p/5489362.html