博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
约瑟夫环问题
阅读量:3908 次
发布时间:2019-05-23

本文共 2935 字,大约阅读时间需要 9 分钟。

问题来历

     据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。

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

问题分析与算法设计

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

题目中30个人围成一圈,因而启发我们用一个循环的链来表示,可以使用结构数组来构成一个循环链。结构中有两个成员,其一为指向下一个人的指针,以构成环形的链;其二为该人是否被扔下海的标记,为1表示还在船上。从第一个人开始对还未扔下海的人进行计数,每数到9时,将结构中的标记改为0,表示该人已被扔下海了。这样循环计数直到有15个人被扔下海为止。

一般形式

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

分析:
(1)由于对于每个人只有死和活两种状态,因此可以用布尔型数组标记每个人的状态,可用true表示死,false表示活。
(2)开始时每个人都是活的,所以数组初值全部赋为false。
(3)模拟杀人过程,直到所有人都被杀死为止。

#include
using namespace std;main(){
bool a[101]={
0}; int n,m,i,f=0,t=0,s=0; cin>>n>>m; do {
++t;//逐个枚举圈中的所有位置 if(t>n) t=1;//数组模拟环状,最后一个与第一个相连 if(!a[t]) s++;//第t个位置上有人则报数 if(s==m)//当前报的数是m {
s=0;//计数器清零 cout<
<<' ';//输出被杀人编号 a[t]=1;//此处人已死,设置为空 f++;//死亡人数+1 } }while(f!=n);//直到所有人都被杀死为止}
#include 
#include
#include
using namespace std;int main(){
int a[8],i,t,k; for(i=1;i<=8;i++) {
a[i]=1; } t=8; k=0; while(t>0) {
for(i=1;i<=8;i++) {
if(a[i]==1) {
k++; if(k==5) {
k=0; a[i]=0; cout<
<

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

    为了讨论方便,先把问题稍微改变一下,并不影响原意:
问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号。
我们知道第一个人(编号一定是(m-1)mod n) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m mod n的人开始):
k k+1 k+2 … n-2,n-1,0,1,2,… k-2
并且从k开始报0。
我们把他们的编号做一下转换:
k --> 0
k+1 --> 1
k+2 --> 2
k-2 --> n-2
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x’=(x+k) mod n
如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的情况 ---- 这显然就是一个倒推问题!
好了,思路出来了,下面写递推公式:
令f表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]
递推公式
f[1]=0;
f[i]=(f[i-1]+m) mod i; (i>1)
有了这个公式,我们要做的就是从1-n顺序算出f的数值,最后结果是f[n]。因为实际生活中编号总是从1开始,我们输出f[n]+1
由于是逐级递推,不需要保存每个f,程序也是异常简单:

#include 
using namespace std;const int m = 3;int main(){
int n, f = 0; cin >> n; for (int i = 1; i <= n; i++) f = (f + m) % i; cout << f + 1 << endl;}

这个算法的时间复杂度为O(n),相对于模拟算法已经有了很大的提高。算n,m等于一百万,一千万的情况不是问题了。可见,适当地运用数学策略,不仅可以让编程变得简单,而且往往会成倍地提高算法执行效率。

转载地址:http://lbqen.baihongyu.com/

你可能感兴趣的文章
Python 递归 list不正确
查看>>
Python copy a list
查看>>
Iteration Vs Recursion Java
查看>>
What are some of the differences between using recursion to solve a problem versus using iteration?
查看>>
subsets
查看>>
Python Nested List Operation
查看>>
Python Binary Search
查看>>
How to append list to second list
查看>>
Write a program to print all permutations of a given string
查看>>
递归回溯
查看>>
穷举递归和回溯算法终结篇
查看>>
Exhaustive recursion and backtracking
查看>>
递归算法的时间复杂度终结篇
查看>>
全排列算法的递归与非递归实现
查看>>
Python Division and Remainders
查看>>
Python Division //
查看>>
BinarySearch
查看>>
二分查找(Binary Search)需要注意的问题,以及在数据库内核中的实现
查看>>
Arithmetic Progression
查看>>
Bisearch Summary
查看>>