约瑟夫环问题
2016-05-13 14:51
387 查看
问题描述:有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代码,用递归实现:
import java.util.*;
public class Main {
public static int ysfh(int n,int m){
if(n<=1)return 0;
return (ysfh(n-n,m)+m)%n;
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
Scanner scan=new Scanner(System.in);
int n=scan.nextInt();
int m=scan.nextInt();
System.out.println("最后剩下的编号为:"+ysfh(n,m));
}
}
测试样例输出为:
10 3
最后剩下的编号为:3
可以看出来,这么复杂的一个问题递归只需要两行代码即可实现,因此在今后的编程的过程中,要灵活运用数学知识。
分析:这就是著名的约瑟夫环问题,关于来历不再说明,这里直接分析解法。
解法一:蛮力法。我曾将在大一学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代码,用递归实现:
import java.util.*;
public class Main {
public static int ysfh(int n,int m){
if(n<=1)return 0;
return (ysfh(n-n,m)+m)%n;
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
Scanner scan=new Scanner(System.in);
int n=scan.nextInt();
int m=scan.nextInt();
System.out.println("最后剩下的编号为:"+ysfh(n,m));
}
}
测试样例输出为:
10 3
最后剩下的编号为:3
可以看出来,这么复杂的一个问题递归只需要两行代码即可实现,因此在今后的编程的过程中,要灵活运用数学知识。
相关文章推荐
- 深入理解约瑟夫环的数学优化方法
- 批处理解约瑟夫环应用题代码
- java 实现约瑟夫环的实例代码
- php解决约瑟夫环示例
- 详解约瑟夫环问题及其相关的C语言算法实现
- C++循环链表之约瑟夫环的实现方法
- 约瑟夫环问题(数组法)c语言实现
- python超简单解决约瑟夫环问题
- Python实现约瑟夫环问题的方法
- Python实现约瑟夫环问题的方法
- 循环链表与约瑟夫环问题
- 判断循环链表
- 【OJ日志】超级约瑟夫
- C语言竞赛——数字序列
- 数据结构--循环链表
- java 实现链表
- 约瑟夫自杀环
- 【HDU 5366】The mook jong 详解
- 简单约瑟夫环 系列(1)【简单一维数组】
- 数据结构(六)——循环链表