约瑟夫环的数学方法解决
编写约瑟夫环程序时会发现,当我们把整个报数过程的人数N变的很大,例如到几百万,虽然在最后还是只剩下两个人报数,但也要循环几百万次才能确定最后留下来的那个人。这样程序执行的效率不高,会占用大量时间去执行循环的过程。有时会发生输出一直等待很长时间才能出来结果。经过查询资料,找到了约瑟夫环的数学解决方法,以及它的算法具体执行的过程来做分享。
我们假设 N是约瑟夫环中的总人数 M是规定报数的第几个人出列,一般我们规定是3。
一般的实现方法是用数组,链表的实现更为简单一些,方便删除数据。
这里使用数学方法,只需要找出来其中的规律即可得出结果,不需要通过数组或链表来实现。
下面详细介绍算法的具体推算过程:
问题:将编号为0~(N–1)这N个人进行圆形排列,按顺时针从0开始报数,报到M–1的人退出圆形队列,剩下的人继续从0开始报数,不断重复。求最后出列者最初在圆形队列中的编号。
这里从0开始,M-1相当于从第一个人报数开始时的M。
下面首先列出0~(N–1)这N个人的原始编号如下:
0 1 2 3 4 … N-3 N-2 N-1
第一个出列人的编号一定是(M–1)%n。
例如,在41个人中,若报到3的人出列,则第一个出列人的编号一定是(3–1)%41=2,注意这里的编号是从0开始的,因此编号2实际对应以1为起点中的编号3。根据前面的描述,m的前一个元素(M–1)已经出列,则出列1人后的列表如下:
0 1 … M-3 M-2 M-1 M M+1 … N-2 N-1
根据规则,当有人出列之后,下一个位置的人又从0开始报数,则以上列表可调整为以下形式(即以M位置开始,N–1之后再接上0、1、2……,形成环状):
M M+1 … N-2 N-1 0 1 … M-3 M-2
按上面排列的顺序重新进行编号,可得到下面的对应关系:
M M+1 … N-2 N-1 0 1 … M-3 M-2
0 1 … … N-3 N-2
即,将出列1人后的数据重新组织成了0~(N–2)共N–1个人的列表,继续求n–1个参与人员,按报数到M–1即出列,求解最后一个出列者最初在圆形队列中的编号。
通过一次处理,将问题的规模缩小了。即,对于N个人报数的问题,可以分解为先求解(N–1)个人报数的子问题;而对于(N–1)个人报数的子问题,又可分解为先求[(N–1)–1]人个报数的子问题,……。
问题中的规模最小时是什么情况?就是只有1个人时(N=1),报数到(M–1)的人出列,这时最后出列的是编号为0的这个人。因此,可设有以下函数:
F(1)=0
那么,当N=2,报数到(M–1)的人出列,最后出列的人是谁?应该是只有一个人报数时得到的最后出列的序号加上M,因为报到M-1的人已出列,只有2个人,则另一个出列的就是最后出列者,可用公式表示为以下形式:
F(2)=F(1)+M
通过上面的算式计算时,F(2)的结果可能会超过N值(人数的总数)。例如,设N=2,M=3(即2个人,报数到2时就出列),则按上式计算得到的值是:
F(2)=F(1)+M=0+3=3
一共只有2人参与,编号为3的人显然没有。怎么办?由于是环状报数,因此当两个人报完数之后,又从编号为0的人开始接着报数。根据这个原理,即可对求得的值与总人数N进行模运算,即:
F(2)=[F(1)+M]%2
验证: F(2)=[F(1)+M]%2
=[(0+3)]%2
=1
即,N=2,M=3(即有2个人,报数到3–1的人出列)时,循环报数最后一个出列的人的编号为1(编号从0开始)。推算一下,当编号为0、1的两个人循环报数时,编号为0的人报的数为0和2,当报到2(M–1)时,编号0出列,最后剩下编号为1的人,所以编号为1的人最后出列。
编号: 0 1
报数:0,2 1
根据上面的推导过程,可以很容易推导出,当N=3时的公式:
F(2)=[F(1)+M]%3
同理,也可以推导出参与人数为N时,最后出列人员编号的公式:
F(N)=[F(N-1)+M]%N
其实,这就是一个递推公式,公式包含以下两个式子:
F(1)=0
F(N)=[F(N-1)+M]%n (N>1)
有了这个递推公式,再来设计程序就很简单了,可以用递归的方法来设计程序,具体代码如下:
/*****************************************************
copyright (C), 2014-2015, Lighting Studio. Co., Ltd.
File name:
Author:Jerey_Jobs Version:0.1 Date:
Description:
Funcion List:
*****************************************************/
#include <stdio.h>
int main(void)
{
int n,m,i,s = 0 ;
printf("输入参与人数N和出列位置M的值\n");
scanf("%d%d",&n,&m);
for(i = 2;i <=n;i++)
{
s=(s+m)%i;
}
printf("最后出列的人最初位置是%d\n",s+1);
return 1;
}