约瑟夫环问题

目录

模拟解决约瑟夫问题

递推解决约瑟夫环问题

递归解决约瑟夫环问题


约瑟夫问题大概是这样的:有n个人围成一圈,编号为 1, 2,....,n 。从第一个人开始报数(从1开始),报到 k 数字的人出圈,然后下一个人又从 1 开始报数,...,以此类推,直到最后只剩下一个人,问这个人的编号是多少。 

模拟解决约瑟夫问题

大概思想:用双端队列模拟这个圈,每次将前k-1个人移至队列尾部,然后删除队列第一个元素,直到剩下一个人。

#include <iostream>
#include <deque>
using namespace std;

int n;          // n 个人
int k;          // 报到k数字的人出圈,从1开始报
deque<int> Q;   // 模拟围成的圈

int main(){

    cin >> n >> k;
    for(int i = 1; i <= n; i++){
        Q.push_back(i);         // 1~n 依次排成一排 :  1 2 3 4 ... n
    }

    while(Q.size() != 1){      // 直到最后只剩一个人退出循环
        for(int i = 0; i < k - 1; i++){ // 将k-1个人移至尾部
            int t = Q.front();  // 得到队列的第一个元素
            Q.pop_front();      // 并删除第一个元素
            Q.push_back(t);     // 将这个元素插入到队列的尾部:2 3 4 ..... n 1
        }
        Q.pop_front();          // 将这个人出圈
    }

    cout << Q.front() << endl;  // 最后就剩下一个人,输出即可
    
    return 0;
}

递推解决约瑟夫环问题

大概思想:

如图,开始的时候,编号依次为 0,1,...,n - 1(图中n=10),然后报到2数字出圈(图中红色数字代表出圈)。然后下一轮重新编号:谁出圈,就从这个人的下一个人开始从 0 编号。

直到最后还剩下一个人,那么他的编号一定是 0(图中黄色框),我们所要求的也就是这个列的第一个数字(黄色列第一个数字)。

我们发现,我们已知的(黄色框)和我们想求的都在同一列,一个在下边,一个在上边,如果能依次递推该多好啊

我们正是通过第 i 轮的编号求出第 i - 1轮的编号,然后依次递推求出来的。那么相邻的两轮编号之间有什么关系呢,我们来开始分析

我们先看【开始】到【第1轮】编号之间有什么关系,如下图:

我们把上一轮的编号叫做 oldNum , 下一轮的编号叫做 newNum,也就是说 oldNum 和 new之间有什么关系呢?

不难发现,他们的关系是  oldNum = (newNum + 2) % 10。

我们再来看看【第1轮】到【第2轮】编号之间的关系,看看能否找到规律,如图:

  

同样的,上一轮的编号为 oldNum,下一轮的编号为 newNum,不难发现,他们之间的关系为: oldNum = (newNum + 2) % 9

和【开始】到【第1轮】之间的关系 oldNum = (newNum + 2) % 10 比较,仿佛如果接着分析【第2轮】到【第3轮】,是不是有 oldNum = (newNum + 2) % 8

通过查表发现,还真是这么回事。变化的只有 % 后面的数字,而 % 前面都是固定不变的。那么这个 % 后面的数字是什么?

其实他就是上一轮中剩下的人数(图中第二列)。

 

我们一开始只知道图中黄色框为 0, 这样我们就能求出黄色框上面框的数字了:此时 newNum = 0(黄色框),可以求出黄色框上面框 oldNum = (newNum + 2) % 2。然后又可以求黄色框上上面框的编号,.... ,就这样,通过迭代直到求出一开始的编号。

#include <iostream>
using namespace std;

int main(){

    int n;       // n个人
    // 这里编号从0开始,即 0 1 2 3 4 ..... n-1
    int k;      // 报到k数字的人出圈,每次从1开始报数
    cin >> n >> k;

    int num = 0;  // 当人数为1个人的时候,那个活下来的人的编号,它总是0,因为每次重新从0开始编号
    // 现在反推当人数为n的时候编号为多少
    for(int current = 2; current <= n; current++){
        // 当人数为current个人的时候那个活下来的编号
        num = (num + k) % current;
        
    }
    // 因为一开始的编号是 从0开始到n-1, 而题目是从1到n编号,所以+1
    cout << num + 1 <<endl;

    return 0;
}

递归解决约瑟夫环问题

从递推到递归的转变不难,就如同斐波那契数列求第 n 项那样,递推是从底向上,而递归是从上至下,递推是从已知推未知,而递归是一开始不知道,就问下一层,直到最底层知道,然后就向上传。

#include <iostream>
using namespace std;

// 还剩下 current 人的那一轮
int Num(int current, int k){
    if(current == 1){
        // 当剩下一个人,他的编号总是0
        return 0;
    }else{
        // 我们现在不知道下一轮的编号, 就问下一层,也就是这里 Num(current-1, k) 的含义
        return (Num(current - 1, k) + k) % current;
    }
}
int main(){

    int n, k;
    cin >> n >> k;
    // 编号 0 1 2 3 .... n-1

    
    // 最后记得+1,因为一开始题目编号规定是1-n。如果题目规定0到n-1,则不需要+1
    cout << Num(n, k) + 1 << endl;

    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值