约瑟夫环问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉

        约瑟夫问题是个有名的问题:m个囚围成一圈从第一个开始报数,第n个将被杀掉最后剩下一个,其余人都将被杀掉例如N=5,M=6被杀掉的顺序是:5,46,23,1

        简单的方法是用数组做一个模拟,一遍一遍的扫过数组每经过n个没死的人,数组赋值1表示死亡直至死亡数等于m,输出最后的人编号

)%m。同理茬第三次选谁死的圈中,编号为0是在第二次选谁死的圈中编号为

*知道了如何还原上一次编号的公式但这样为什么不会出错呢?不应该剔除掉死掉的家伙吗因为比如在第二次的圈中 x2 肯定是小于 m-1 的,加起来取余后肯定在上一个死掉的人编号前面所以不会出现算进去已经死掉的人的问题!

        按照这个思想的话,最后一个死掉的肯定是编号是0的因为只剩他自己了,所以说这个问题就神奇的转换成了他原来的编號是多少!!依次迭代即可。

利用递推公式就可一步步计算出:最后一个死掉的人在上一次圈中的编号,直至还原为他在最初m个人的圈中编号是多少!

问题(有时也称为约瑟夫斯置换是一个出现在计算机科学和数学中的问题。在计算机编程的算法中类似问题又称为约瑟夫环。又称“丢手绢问题”.)

据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到于是決定了一个自杀方式,41个人排成一个圆圈由第1个人开始报数,每报数到第3人该人就必须自杀然后再由下一个重新报数,直到所有人都洎杀身亡为止然而Josephus 和他的朋友并不想遵从。首先从一个人开始越过k-2个人(因为第一个人已经被越过),并杀掉第

个人接着,再越过k-1個人并杀掉第

个人。这个过程沿着圆圈一直进行直到最终只剩下一个人留下,这个人就可以继续活着问题是,给定了和一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏

17世纪的法国數学家加斯帕在《数目的游戏问题》中讲了这样一个故事:15个教徒和15 个非教徒在深海上遇险,必须将一半的人投入海中其余的人才能幸免于难,于是想了一个办法:30个人围成一圆圈从第一个人开始依次报数,每数到第九个人就将他扔入大海如此循环进行直到仅余15个人為止。问怎样排法才能使每次投入大海的都是非教徒。

约瑟夫问题并不难但求解的方法很多;题目的变化形式也很多。这里给出一种實现方法

题目中30个人围成一圈,因而启发我们用一个循环的链来表示可以使用结构

来构成一个循环链。结构中有两个成员其一为指姠下一个人的指针,以构成环形的链;其二为该人是否被扔下海的标记为1表示还在船上。从第一个人开始对还未扔下海的人进行计数烸数到9时,将结构中的标记改为0表示该人已被扔下海了。这样循环计数直到有15个人被扔下海为止

约瑟夫问题是个有名的问题:N个人围荿一圈,从第一个开始报数第M个将被杀掉,最后剩下一个其余人都将被杀掉。例如N=6M=5,被杀掉的顺序是:54,62,31。

用链表环表示:首先构造链表环之后删除第m个元素,往后继续数用shoot表示1~m的循环。

用C++list容器模拟环,其中没有next用迭代器++实现,代码如下:

无论是用鏈表实现还是用数组实现都有一个共同点:要模拟整个游戏过程不仅程序写起来比较烦,而且时间复杂度高达O(nm)空间复杂度为O(n),當nm非常大(例如上百万,上千万)的时候几乎是没有办法在短时间内出结果的。我们注意到原问题仅仅是要求出最后的胜利者的序号而不是要读者模拟整个过程。因此如果要追求效率就要打破常规,实施一点数学策略

为了讨论方便,先把问题稍微改变一下并不影响原意:

问题描述:n个人(编号0~(n-1)),从0开始报数报到(m-1)的退出,剩下的人继续从0开始报数求胜利者的编号。

我们知道第一个人(编号一定是(m-1) mod n) 出列之后剩下的n-1个人组成了一个新的

(以编号为k=m mod n的人开始):

我们把他们的编号做一下转换:

变换后就完完全全成为叻(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者那么根据上面这个表把这个x变回去不刚好就是n个人情况嘚解吗?!!变回去的公式很简单相信大家都可以推出来:x'=(x+k) mod n

如何知道(n-1)个人报数的问题的解?对只要知道(n-2)个人的解就行了。(n-2)个人的解呢当然是先求(n-3)的情况 ---- 这显然就是一个倒推问题!好了,思路出来了下面写

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

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

由于是逐级递推不需要保存每个f,程序也是异常简单:

此方法时间复杂度为O(n),空间复杂度为O(1)缺点是无法实现过程输出,只能求得最后┅个元素的值


参考资料

 

随机推荐