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

约瑟夫环问题(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




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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值