约瑟夫问题(Josephus Problem)

背景
相传公元一世纪著名犹太历史学家约瑟夫在罗马人占领乔塔帕特後,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。

约瑟夫问题描述
设有n个犯人坐成一个圈(编号1~n),从第k(1<=k<=n)个人开始报数,数到m的人将被处决掉,接着从下一个人开始从新报数,数到m的人再被处决,如此循环,直至剩下一名犯人. 求处决犯人的顺序和最后的幸存者编号.
我们可以用代码模拟整个过程从而得出正确的答案:

模拟法
<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1/**////<summary>
2///解约瑟夫问题
3///</summary>
4///<paramname="total">环节点总数</param>
5///<paramname="eliminate">每次排除数到eliminate的人</param>
6///<paramname="start">起始节点号</param>
7///<returns>最后剩下的节点号</returns>

8privatestaticintSolveJosephus(inttotal,inteliminate,intstart)
9{
10int[]killed=newint[total];
11intOrderNum=1;//序号
12intSuffix=(start-1)%total;//Suffix标记每次计数的开始点,做为killed的下标
13intCountNum=0;//killed数
14intSurvivor=0;
15
16Console.WriteLine("Thekillorderis:");
17
18while(CountNum<total-1)
19{
20if(((OrderNum%eliminate)|(killed[Suffix]))==0)
21{
22killed[Suffix]=1;//标记为已kill
23Console.Write("{0,-5:d}",Suffix+1);
24CountNum++;
25OrderNum++;
26}

27if(killed[Suffix]!=1)
28{
29OrderNum++;
30}

31Suffix++;
32Suffix%=total;
33}

34
35for(inti=0;i<total;i++)
36if(killed[i]==0)
37{
38Survivor=i;
39Console.WriteLine("\nSurvivor'snumber:{0}",i+1);
40}

41returnSurvivor;
42}

当然, 这并不是最好最高效的解法.这里我们回到广义的约瑟夫问题来分析一下:
如果有n个人围成一圈而坐,每个人的位置都带编号,编号从1到n(没有重复的),从第k个位置开始数数,当数到m时,那个人退出圈子,再从退出的那个人的下一个位置开始数(假定是顺时针数的),一直到剩下r个人。

进一步分析

n个人玩这个游戏的时候,假设最后剩下的r个人中,其中一个人占据了第p个位置,那当我们以n + 1个人开始玩游戏的时候,显然,这第n + 1个人希望被安排到第p + m个位置,因为如果第p个人是安全的,那么至少这第(p + m)MOD (n + 1)个位置上的人也是安全的,他的理由是“既然现在是n + 1个人玩,仍然只能留r个人,那么只要在n个人剩余的r个人里面有一个人在我之前退出就可以了,所以我要加在这第r个人中某个人的后面m处”。对应的,如果n个人玩的时候,第q个位置上的人退出了圈子,那n + 1个人玩的时候第(q + m)MOD (n + 1)个位置上的人也得退出圈子。根据前面的递推公式,显然我们可以通过逆推得到最终解,原先的约瑟夫函数可以写成如下形式:

代数法
<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->privatestaticintSolveJosephus2(inttotal,inteliminate,intstart)
{
intj,k=0;
int[]count=newint[total+1];
int[]s=newint[total+1];
for(inti=0;i<total+1;i++)
{
s[i]
=i;
}

for(inti=total;i>1;i--)
{
start
=(start+eliminate-1)%i;
if(start==0)
start
=i;
count[k]
=s[start];
k
++;
for(j=start+1;j<=i;j++)
s[j
-1]=s[j];
}

count[k]
=s[1];

Console.WriteLine(
"Thekillorderis:");
for(inti=0;i<total-1;i++)
{
Console.Write(
"{0,-5:d}",count[i]);
}

Console.WriteLine(
"\nSurvivor'snumber:{0}",count[total-1]);
returncount[total-1];
}

推论
从上面的分析中已经知道,每次添加一个人到游戏中,原来剩余的那r个人的位置就会“后移”m个单位,因为新加的人使得ta之前的第m个人退出了圈子。所以我们可以进一步地作出这样的推论:

假设n个人玩游戏,最后一个幸存者(即r = 1时)占据了编号为p的位置,而在n + x个人玩的时候,最后一个幸存者占据第y个位置。那么y = (p + mx )MOD (n + x)。
这样我们就可以根据不同的初值n迅速计算出最后一个人的位置了。

参考资料

1. W.W.Rouse Ball and H.S.M.Coxeter, Mathematical Recreations and Essays , Dover, 1987
2.Wikipedia,Josephus Problem,http://en.wikipedia.org/wiki/Josephus_problem

黄季冬
2009年8月4日

你可能感兴趣的:(游戏,J#)