C++ 约瑟夫环问题详解与代码实现

听雨眠
听雨眠
发布时间:2026-10-16| 6人浏览过
原创

约瑟夫环是一个经典的理论数学和计算机科学问题,描述为 n 个人围成一圈,按固定步长淘汰人员直至剩下一人。本文深入解析该问题的核心逻辑,首先探讨步长为 2 时的特殊数学规律,随后推导适用于任意步长的通用递推公式,并附带完整的 C++ 递归与迭代代码实现。

1. 什么是约瑟夫环

约瑟夫环(Josephus Problem)源于一个历史故事:N 个人围成一圈,从第一个人开始报数,每次数到第 M 个人的出列,然后从下一个人重新开始报数,直到只剩最后一个人。

在编程面试和算法竞赛中,这个问题通常转化为:

  • n:总人数,编号从 0 到 n-1。
  • m(或 q):步长,即每次数到的数字,对应位置的人被淘汰。
  • 目标:计算最后剩余那个人的原始编号。

2. 特殊情况:当步长 q = 2 时

当每次剔除第 2 个人时(q=2),存在一个简洁的数学规律,无需使用复杂的链表或模拟过程即可直接得出结果。

第1步:分析 2 的幂次情况

假设总人数 n 恰好是 2 的幂次(即 n = 2^k):

  1. 第一轮:所有人围成圈,依次剔除偶数位(2, 4, 6...),剩下的人正好是奇数位(1, 3, 5...)。
  2. 第二轮:因为剩下的总人数减半且仍为 2 的幂次,继续剔除新的偶数位,最后幸存的永远是编号为 1 的人。

结论:若 n = 2^k,则幸存者编号 J(n) = 1。

第2步:推导非 2 的幂次情况

当 n 不是 2 的幂次时,可以将其表示为:
n = 2^k + t

其中 2^k 是不超过 n 的最大 2 的幂次,t 为余数。

推导逻辑:

  1. 在第一轮过程中,我们需要先剔除 t 个人,使得剩余人数恰好变为 2^k。
  2. 因为步长是 2,剔除第 1、3、5... 个偶数位,共剔除 t 人后,下一个轮到报数的人成为了新一圈的起点。
  3. 此时剩下的人数为 2^k,根据前文结论,在新圈中以新起点为 1 号位的话,幸存者是其新编号中的 1。
  4. 回到原始编号:我们剔除了 t 个偶数,最后被剔除的是第 2t 个人。下一位幸存者原本是第 2t + 1 个人。

最终公式:
J(2^k + t) = 2 * t + 1

第3步:实例验证

例如 n = 9:

  • 找到最大的 2 的幂次:8 (2^3)。
  • 计算余数:t = 9 - 8 = 1。
  • 代入公式:J(9) = 2 * 1 + 1 = 3。

即编号为 3 的人最后存活(注意:若题目编号从 1 开始,答案为 3;若从 0 开始,则为 2。通常数学公式基于 1-indexed 计算较直观,代码实现常转为 0-indexed)。

3. 通用情况:当步长 q ≠ 2 时

对于任意步长 m(原文称 q),我们可以利用动态规划/递推的思想来求解。这是解决约瑟夫环最通用的方法。

第4步:建立递推关系

定义函数 J(m, n) 为 n 个人、步长为 m 时最后幸存者的索引(编号从 0 开始)。

思考过程:

  1. 正向删除:从 0, 1, ..., n-1 这 n 个数中,第一个被杀掉的人是索引 (m - 1) % n。
  2. 状态转移:杀掉一人后,剩下 n-1 个人。为了保持逻辑一致性,我们将下一个活着的人重新映射为新的 0 号位。
  3. 逆向还原:如果我们知道了 n-1 个人时的幸存者索引 f(n-1),那么在原来的 n 个人的序列中,这个位置的索引应该是多少?
    • 由于整个序列循环右移了 m 位(或者说起始点偏移了 m)。
    • 还原公式为:f(n) = (f(n-1) + m) % n。

初始条件:
当只有 1 个人时(n=1),幸存者毫无疑问就是索引 0。即 f(1) = 0。

4. C++ 代码实现

根据上述递推公式,我们可以用递归或迭代两种方式实现。推荐使用迭代方式,空间复杂度为 O(1)。

第5步:编写递归版本

直接体现 f(n) = (f(n-1) + m) % n 的数学定义:

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

// 递归求解约瑟夫环
int yuesefu_recursive(int n, int m) {
    if (n == 1) {
        return 0; // 基础情况:1个人,索引为0
    }
    // 递推公式:(上一步的结果 + 步长) % 当前人数
    return (yuesefu_recursive(n - 1, m) + m) % n;
}

int main() {
    int a, b;
    cin >> a >> b; // a为总人数n,b为步长m
    cout << "Index of survivor (0-based): " << yuesefu_recursive(a, b) << endl;
    return 0;
}

第6步:编写迭代版本(推荐)

从 n=1 开始不断向上推导至 n,避免递归栈溢出风险:

#include <iostream>
using namespace std;

int yuesefu_iterative(int n, int m) {
    int result = 0; // f(1) = 0
    for (int i = 2; i <= n; i++) {
        // 逐步更新幸存者在当前规模 i 下的索引
        result = (result + m) % i;
    }
    return result;
}

int main() {
    int a, b;
    cin >> a >> b;
    cout << "Index of survivor (0-based): " << yuesefu_iterative(a, b) << endl;
    
    // 如果题目要求编号从1开始,只需将结果加1
    cout << "Position (1-based): " << yuesefu_iterative(a, b) + 1 << endl;
    
    return 0;
}

总结

约瑟夫环问题可以通过数学规律快速求解特例,也可以通过递推公式解决一般情况。掌握 f(n) = (f(n-1) + m) % n 这一核心公式,便能高效处理此类环形淘汰算法题。

以上就是 C++ 约瑟夫环问题案例详解的详细内容,更多关于 C++ 约瑟夫环的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。