约瑟夫问题(数组+队列+公式迭代+递归)

这篇博客详细介绍了约瑟夫环问题的四种解决方法:数组、队列、公式迭代和递归。通过实例展示了每种方法的实现代码,包括C++实现的数组和队列版本,以及公式和递归的思路解析。博客强调了数组和队列方法的易理解和实用性,同时也探讨了递归和公式法的逻辑和效率。
摘要由CSDN通过智能技术生成

约瑟夫问题(数组+队列+公式迭代+递归)

约瑟夫环问题起源于一个犹太故事:
罗马人攻占了桥塔帕特,41个人藏在一个山洞中躲过了这场浩劫。这41个人中,包括历史学家Josephus(约瑟夫)和他的一个朋友。剩余的39个人为了表示不向罗马人屈服,决定集体自杀。大家制定了一个自杀方案,所有这41个人围成一个圆圈,由第一个人开始顺时针报数,每报数为3的人就立刻自杀,然后再由下一个人重新开始报数,仍然是每报数为3的人就立刻自杀……,直到所有的人都自杀身亡为止。约瑟夫和他的朋友并不想自杀,于是约瑟夫想到了一个计策,他们两个同样参与到自杀方案中,但是最后却躲过了自杀。

简化一下问题(不这么血腥的例题):


设有n个人围坐一圈,编号为1到n,现从编号为1的人开始报数,数到m的人出列,接着从出列的下一个编号(人)开始重新报数,数到m的人出列,如此下去,直到只剩下最后一个人,并输出其编号。


例如输入

4 5

输出结果

2

用数组的思路很简单(这也是刚开始接触这道题我的做法)

基本思路(容易理解):一开始,将全部数字存入数组中并全部初始化为0,定义一个变量c表示当前数到的人的编号,再定义一个变量k表示从1开始数到m的人的个数,再再定义一个变量s表示出圈的人的个数。
代码如下:

#include<iostream>
using namespace std;
int main()
{
    int n,m;//n表示多少个人参与游戏,m表示数到多少个人出圈
    cin>>n>>m;
    int arr[1000]= {0}; //0表示人还在圈中
    int k=0;//报数数到几
    int c=0;//当前人的编号
    int s=0;//出圈的人数和
    while(s!=n)
    {
        c++;
        if(c>n)c=1;//大于总人数时,重新开始报数
        if(arr[c]==0)
        {
            k++;
            if(m==k)
            {
                arr[c]=1;//标记后下次不报数
                s++;
                //cout<<c<<" ";
                k=0;//使下一个人从1开始报数
            }
        }
    }
    cout<<c;
}

编译结果如下:
在这里插入图片描述


队列的方法(C++)

相比于数组,队列的方法更好写,同时也很好理解。

解法:将人数按编号1-n依次存入队列中,然后做双重循环,将队头元素放到队尾,每隔m-1个退出循环,弹出队

头元素(相当于杀掉的人),再一直循环到只剩最后一人为止。

#include<iostream>
#include<queue>
using namespace std;
int main()
{
    queue<int>Q;
    int i,j,n,m;
    cin>>n>>m;
    for(i=1;i<=n;i++)
    {
        Q.push(i);
    }
    while(n!=1)
    {
        for(j=1;j<m;j++)//相当于j<=m-1;起计数的作用
        {
            Q.push(Q.front());//队头元素放到队尾
            Q.pop();//弹出队头元素
        }
        Q.pop();//杀掉的人
        n--;//总人数减一
    }
    cout<<Q.front();
}

运行结果如下:
在这里插入图片描述

当然这里队列queue也可以转换为数组,即用f,r++。。。来做。这里就不写了。


找规律的方法(公式法)

理解这个递推式的核心在于关注胜利者的下标位置是怎么变的。每杀掉一个人,其实就是把这个数组向前移动了M位。然后逆过来,就可以得到这个递推式。

这里要真正理解可以参考这篇文章
点击打开大佬的文章

#include<iostream>
using namespace std;
int main()
{
    int n,m;//n表示多少个人参与游戏,m表示数到多少个人出圈
    int k=0;
    cin>>n>>m;//输入n和m
    for(int i=2;i<=n;i++)
    {
        k=(k+m)%i;
    }
    cout<<k+1;//记录的是下标,所以要加上1
}

编译结果如下:
在这里插入图片描述

和递归的递推公式相比,似乎看上去很像,但它们之间的思想有着本质的区别。递归推导式是从上到下,不断进入

递归再回溯;而这种推导式,是从下到上,符合正常的逻辑思维,比较容易理解。


递归的思想比较复杂(其实就是公式法),但代码量炒鸡短。

f(n,k)中的n为未报过数的人数,k为报数的数,等号右边就是最后剩下的人的编号。(从编号1开始)

迭代法 k=5

f(n,k) = [f(n-1,k) + k] % n

f(4,5)=2

f(3,5)=1

这样下去,倒推向前。

拓展:

从第一个士兵开始remove ;

f(3,5)=3

f(4,5)=2

显然和前面的规则而言,相当于不是从1开始数数,而是从2开始数数。所以公式不変!只是人数记得改成n-1

f(n,k) = [f(n-1,k) + k] % (n-1)

f(4,5) = 2

= [f(3,5)+5] % (4-1)

=(3+5)%3

=2

这里遍历多加上了一个变量n用于递归结束的标志。

#include<iostream>
using namespace std;
int recursion(int sum,int value,int n)
{
    if(n==1)return(sum+value-1)%sum;//递归结束的标志:剩下最后一个人
    else return (recursion(sum-1,value,n-1)+value)%sum;
}
int main()
{
    int n,m;//n表示多少个人参与游戏,m表示数到多少个人出圈
    cin>>n>>m;
    cout<<recursion(n,m,n)+1;
}

运行结果如下:
在这里插入图片描述


当然除了以上方法外,还有就是循环链表的方法,较为繁琐。这里以后有空再更新。

主要是链表的代码较为复杂又臭又长。


总结

其实,要你直接写出约瑟夫问题的题目不多,更多的是将几种方法和约瑟夫问题结合起来的题目。因此,个人认为数组,队列的放法必须要掌握,递归和公式法因为不同题目有不同问题,会比较难理解也会很难写出来,链表嘛要是题目没明确规定,还是别了吧!

以上纯个人笔记,若有不对的地方,请及时纠正。

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值