您的位置:首页 > 其它

详细阐述约瑟夫环问题(报数出队问题)

2015-10-05 16:51 363 查看
约瑟夫环问题(Josephus)

      用户输入M,N值,从1至N开始顺序循环数数,每数到M输出该数值,直至全部输出。写出C程序。(约瑟夫环问题 Josephus)

直接上代码:

1 #include <stdio.h>
2 int main()
3 {
4 int n, m, i, s = 0;
5 printf ("N M = ");
6 scanf("%d%d", &n, &m);
7 for (i = 2; i <= n; i++)
8 {
9 s = (s + m) % i;
10 }
11 printf ("\nThe winner is %d\n", s+1);
12 }

刚接触这类问题的同学是不是有一种“我靠,为什么这么少”的感觉,然而只要这么少。

接下来讲解一下代码,既然作为一个经典的问题,那么此算法也当然不仅仅是对游戏过程的模拟了,更是对数学本质的一个分析。

为了讨论方便,先把问题稍微改变一下,并不影响原意:
问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号。

我们知道第一个出列的人编号一定是 m%n
-1。那么剩下的n-1个人便可以看做组成了一个新的约瑟夫环(以编号为k=m%n的人开始):

  k  k+1  k+2  ... n-2, n-1, 0, 1, 2, ... k-2并且从k开始报0。

现在我们把n-1个人的时候的编号做一下转换:

k     --> 0
k+1   --> 1
k+2   --> 2
...
...
k-2   --> n-2

变换后就完完全全成为了(n-1)个人报数的子问题!(这便不是让我们倒推吗?

假如我们知道这个子问题的解:例如A[n-1]是在n-1个人中的最终胜利者,那么根据上面对A[n-1]进行转换
 便刚好就是n个人情况的解。变回去的公式很简单,如下:

A
=(A[n-1]   +k)%n。(k是在n个人中第一个出列的人的编号,此处这个公式涉及了上面叙述到的编号的转换,需要搞懂了才能理解)

由于k=m%n,那么公式也可以写为A
=(A[n-1]
  +m)%n。为什么呢?很容易理解,7%3=1 , 1%3=1。

如此也是得出了递推公式:

A[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是A

A[1]=0;

A[2]=(A[1]+m)%2;
A[i]=(A[i-1]+m)%i;  (i>1)

有了这个公式,我们要做的就是从1-n顺序算出A[i]的数值,最后结果是A
。因为实际生活中编号总是从1开始,我们输出A
+1
由于是逐级递推,不需要保存每个A[i],于是便得到了最终的程序:

1 #include <stdio.h>
2 int main()
3 {
4 int n, m, i, s = 0;
5 printf ("N M = ");
6 scanf("%d%d", &n, &m);
7 for (i = 2; i <= n; i++)
8 {
9 s = (s + m) % i;
10 }
11 printf ("\nThe winner is %d\n", s+1);
12 }

参考博客:http://blog.csdn.net/ZCSYLJ/article/details/6830703

本人博客,算法均为新手,闻过则喜,望各前辈不吝指点批评。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法 acm 约瑟夫问题