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

来源:互联网 发布:mac抹掉进程失败 编辑:程序博客网 时间:2024/05/16 17:27

约瑟夫环问题(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[n]=(A[n-1]   +k)%n。(k是在n个人中第一个出列的人的编号,此处这个公式涉及了上面叙述到的编号的转换,需要搞懂了才能理解)


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


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

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

A[1]=0;

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

有了这个公式,我们要做的就是从1-n顺序算出A[i]的数值,最后结果是A[n]。因为实际生活中编号总是从1开始,我们输出A[n]+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




本人博客,算法均为新手,闻过则喜,望各前辈不吝指点批评。


1 0