问题描述:有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
可以看出来,这么复杂的一个问题递归只需要两行代码即可实现,因此在今后的编程的过程中,要灵活运用数学知识。