约瑟夫问题讲解

文章讲述了约瑟夫问题的变种及其算法设计,如30人围成一圈的杀人游戏,利用循环链和布尔数组模拟过程,展示了如何用编程解决这类经典问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

 

【问题分析与算法设计】

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

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

 

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

分析:

(1)由于对于每个人只有死和活两种状态,因此可以用布尔型数组标记每个人的状态,可用true(或者1)表示死,false(或者0)表示活。

(2)开始时每个人都是活的,所以数组初值全部赋为false。

(3)模拟杀人过程,直到所有人都被杀死为止。

约瑟夫经典代码:

#include<bits/stdc++.h>

using namespace std;

int main(){

    bool a[101]={0};

    int n,m,i,f=0,t=0,s=0;

    cin>>n>>m;
     while(f!=n){  //直到所有人都被杀死为止
    

        ++t;//逐个枚举圈中的所有位置

        if(t>n)

            t=1;//数组模拟环状,最后一个与第一个相连

        if(a[t]==0)

            s++;//第t个位置上有人则报数

        if(s==m)//当前报的数是m

        {

            s=0;//计数器清零

            cout<<t<<' ';//输出被杀人编号

            a[t]=1;//此处人已死,设置a[t]为1

            f++;//死亡人数+1

        }

    }

  return 0;

}

参考例题:

第1题     改变数组的项2

有n个正整数放到数组里,现在将数组中,个位数是1的数,变成0,然后从大到小输出新数组。

输入格式

第一行1个正整数:n,范围在[1,100]。
 第二行n个不同的正整数:范围在[1,10000]。

输出格式

n个正整数,两个之间用一个空格隔开。

输入/输出例子1

输入:

5

23 541 26 145 627

输出:

627 145 26 23 0

样例解释

代码如下:

#include<bits/stdc++.h>
using namespace std;
int n,a[105];
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=1;i<=n;i++)
    {
        if(a[i]%10==1)a[i]=0;
    }
    sort(a+1,a+n+1);
    for(int i=n;i>=1;i--)
        cout<<a[i]<<" ";
    return 0;
}
第2题     改变数组的项3

有n个正整数放到a数组里,现在将a数组赋值给b数组,再将b数组接到a数组的后面,组成一个新数组。

现将新数组,从第一个开始,每间隔m个数的值变成0,输出改变后的数组。

输入格式

第一行2个正整数:n,m,范围都在[1,100]。
第二行n个不同的正整数:范围在[1,1000]。

输出格式

n*2个正整数,两个之间用一个空格隔开。

输入/输出例子1

输入:

5 2

1 2 3 4 5

输出:

0 2 3 0 5 1 0 3 4 0

样例解释

代码如下:

#include<bits/stdc++.h>
using namespace std;
int n,m,a[205],b[1005];
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        a[n+i]=a[i];
    }
    for(int i=1;i<=n*2;i=i+m+1)
    {
        a[i]=0;
    }
    for(int i=1;i<=n*2;i++)
        cout<<a[i]<<" ";
    return 0;
}
第3题     约瑟夫问题2

n个人排成一圈。从某个人开始,按顺时针方向依次编号。从编号为1的人开始顺时针"一二……"报数,报到2的人退出圈子。这样不断循环下去,圈子里的人将不断减少。由于人的个数是有限的,因此最终会剩下一个人。试问最后剩下的人最开始的编号。

输入格式

一个正整数n,表示人的个数。输入数据保证数字n<=1000。

输出格式

一个正整数。它表示经过“一二……”报数后最后剩下的人的编号。

输入/输出例子1

输入:

9

输出:

3

样例解释

当n=9时,退出圈子的人的编号依次为:

2 4 6 8 1 5 9 7

最后剩下的人编号为3

代码如下:

#include<bits/stdc++.h>
using namespace std;
bool a[101]={0};
int n,m=2,x=0,t=0,z=0;
int main(){
    cin>>n;
    while(x!=n)
    {
        t=t+1;
        if(t>n)t=1;
        if(a[t]==0)z=z+1;
        if(z==m)
        {
            z=0;
            a[t]=1;
            x++;
        }
    }
    cout<<t;
    return 0;
}
第4题     约瑟夫"密码问题"

编号为1、2、3、...、N的N个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。从指定编号为1的人开始,按顺时针方向自1开始顺序报数,报到指定数M时停止报数,报M的人出列,并将他的密码作为新的M值,从他在顺时针方向的下一个人开始,重新从1报数,依此类推,直至所有的人全部出列为止。第一个指定的数M,为第一个人所持有的密码。

请设计一个程序求出出列的顺序,其中N,M<=30。

输入格式

第一行1个整数 N,表示人数。

第二行N个整数,表示每人手上的密码值

输出格式

N个数,两个相邻的数字用空格隔开

输入/输出例子1

输入:

2 2 2

输出:

2 1 3

输入/输出例子2

输入:

3 5 2 3

输出:

3 1 2 4

样例解释

代码如下:

#include<bits/stdc++.h>
using namespace std;
bool a[101]={0};
int b[101],n,m,x=0,t=0,z=0;
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>b[i];
    m=b[1];
    while(x!=n)
    {
        t=t+1;
        if(t>n)t=1;
        if(a[t]==0)z=z+1;
        if(z==m)
        {
            z=0;
            cout<<t<<" ";
            m=b[t];
            a[t]=1;
            x++;
        }
    }
    return 0;
}
第5题     猴子选大王

n只猴子要选大王,选举方法如下:所有猴子按1,2,…,n编号并按照顺序围成一圈,从第k个猴子起,由1开始报数,报到m时,该猴子就跳出圈外,下一只猴子再次由1开始报数,如此循环,直到圈内只剩下一只猴子时,这只猴子就是大王。

输入格式

三个整数,猴子总数n(n<1000),起始报数的猴子编号为k,出局数字为m(m<100)

输出格式

猴子大王的编号

输入/输出例子1

输入:

10 5 3

输出:

8

样例解释

代码如下:

#include<bits/stdc++.h>
using namespace std;
bool a[1001]={0};
int n,m,k,x=0,t=0,z=0;
int main(){
    cin>>n>>k>>m;
    t=k-1;
    while(x!=n)
    {
        t=t+1;
        if(t>n)t=1;
        if(a[t]==0)z=z+1;
        if(z==m)
        {
            z=0;
            a[t]=1;
            x++;
        }
    }
    cout<<t;
    return 0;
}
第6题     慈善的约瑟夫

你一定听说过约瑟夫问题吧?即从N个人中找出唯一的幸存者。现在老约瑟夫将组织一个皆大欢喜的新游戏,假设N个人站成一圈,从第1人开始交替的去掉游戏者,但只是暂时去掉(例如一开始的暂时去掉2),直到最后剩下唯一的幸存者为止。幸存者选出后,所有比幸存者号码高的人每人得到1个金币,永久性离开。其余剩下的将重复以上的游戏过程,比幸存者号码高的人每人得到1个金币后离开。经过这样的过程后,一旦人数不再减少,则最后剩下的那些人将得到2个金币。请你计算一下老约瑟夫一共要付出多少个金币?

输入格式

一行一个正整数N表示人数。

输出格式

一行一个正整数表示共需支付的金币个数。

输入/输出例子1

输入:

10

输出:

13

样例解释

1<=N<=100000

代码如下:

#include<bits/stdc++.h>
using namespace std;
long long n,i=2;
long long a[1000005],b[1000005];
int main(){
    cin>>n;
    a[i]=1,b[1]=1;
    while(i<=n)
    {
        b[i]=(b[i-1]+1)%i+1;
        if(b[i]==i)a[i]=i;
        else a[i]=a[b[i]];
        i++;
    }
    cout<<a[n]+n;
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值