9.2练习题5 约瑟夫问题 题解

题目出处:洛谷 P1996 ,略有修改。

题目描述

约瑟夫问题是一个非常经典的问题。
n个人(n<=100)围成一圈,从第一个人开始报数,数到m的人出列,再由下一个人重新从1开始报数,数到m的人再出圈,……依次类推,直到所有的人都出圈,请输出依次出圈人的编号。

输入格式

输入一行包含两个正数 (n)(m) ,以一个空格分隔 (1 le n,m le 100)

输出格式

输出依次出圈人的编号,两两之间有一个空格分隔(最后没有空格,可参见样例)。

样例输入

10 3

样例输出

3 6 9 2 7 1 8 5 10 4

题目分析如下

约瑟夫环是一个比较经典的问题。还存在数学解法,如果感兴趣的话可以上百度搜索一下《约瑟夫环的数学解法》,会有很多博客讲解这个的(主要是汪老师懒得写囧~)。
但是这里呢我们主要来分析这个问题的模拟解法。
我们可以用很多我们学过的线性数据结构来模拟这个问题。
我们可以用数组,可以用链表,也可以用队列。

解法一:数组模拟
这种方法是最原始最暴力的一种解法,首先,在知道 (n)(m) 的前提下,我们开一个数组 (out[])(out[i]) 用于表示第 (i) 个人是否已经离开了这个环, (out[i] = true) 表示这个人已经出去了; (out[i] = false) 表示这个人还在环里面。
一开始所有的 (out[i]) 都为 false,因为一开始大家都在环里面。
同时我开一个变量 (id) 用于表示当前报数的人的编号,一开始 (id = 1)
然后我循环 (n) 轮(每一轮肯定会出环一个人),每一轮开始时我要初始化一个变量 (cnt = 0) ,用于表示当前已经报数的人的数量。
然后接下来我就遍历 (id) ,如果 (out[i] == true) (说明第 (id) 个人当前没有出环),那么计数 (cnt++)
如果此时 (cnt == m) ,说明已经报数累计了 (m) 人次,我只需要输出 (id) ,同时令 (out[id] = true) 即可(说明第 (id) 个人已经出去了),然后结束该轮。
否则, (id ++) ,如果 (id > n) ,则令 (id = 1)(因为是环,所以第 (n) 个人报完数之后,又会回到第 (1) 个人继续报数)。
实现代码如下:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 110;
bool out[maxn];
int n, m;
int main() {
    cin >> n >> m;
    int id = 1; // id表示当前报数的人的编号
    for (int i = 0; i < n; i ++) {  // 循环n轮,每次出去一个人
        int cnt = 0;    // 表示当前这轮报数的人的数量
        while (cnt < m) {
            if (!out[id]) {
                cnt ++;
                if (cnt == m) break;
            }
            id ++;
            if (id > n) id = 1;
        }
        if (i) putchar(' ');
        out[id] = true; // 第id个人出环
        cout << id;
    }
    cout << endl;
    return 0;
}

解法二:队列模拟
因为大家可能都用过STL提供给我们的模拟队列的queue,所以这里再讲解一下使用queue来模拟约瑟夫环问题的方法。
使用队列来模拟约瑟夫环问题的解题思路如下:
首先将 1 到 n 这 n 个元素一次入队。
接下来进行 n-1 轮,每轮先循环 m-1 次,每次将队首元素pop出来的同时在push进队尾,这 m-1次循环之后的队首元素就是报 m 个那个人的编号,输出队首元素,然后将队首元素pop出队列(这个元素就不用再push进队列了)。
n-1 轮之后,队列中只剩下了一个人,就是我们的队首元素,我们只需要输出它,我们的1程序就可以结束了。
实现代码如下:

#include <bits/stdc++.h>
using namespace std;
int n, m;
queue<int> que;
int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) que.push(i);
    for (int i = 1; i < n; i ++) {  // 循环n-1轮,每轮出去一个人
        for (int j = 0; j < m-1; j ++) {  // 前m-1个报数的人不需要出去
            que.push(que.front());
            que.pop();
        }
        cout << que.front() << " ";
        que.pop();  // 第m个报数的人出去
    }
    cout << que.front() << endl;    // 最后队列中只有一个人
    return 0;
}

不过这里大家要注意的是,老师的数据是没有问题的,但是洛谷原题的数据是有问题的,老师的这个程序在洛谷上面第3组测试样例没过,后来看了半天才发现原来第3组数据的输入是“0 0”,所以如果你要使用这个代码去提交到洛谷的话记得 (n = 0) 的时候要特殊判断。

原文地址:https://www.cnblogs.com/zifeiynoip/p/11524147.html