C++ 约瑟夫环问题详解与代码实现
约瑟夫环是一个经典的理论数学和计算机科学问题,描述为 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):
- 第一轮:所有人围成圈,依次剔除偶数位(2, 4, 6...),剩下的人正好是奇数位(1, 3, 5...)。
- 第二轮:因为剩下的总人数减半且仍为 2 的幂次,继续剔除新的偶数位,最后幸存的永远是编号为
1的人。
结论:若 n = 2^k,则幸存者编号 J(n) = 1。

第2步:推导非 2 的幂次情况
当 n 不是 2 的幂次时,可以将其表示为:
n = 2^k + t
其中 2^k 是不超过 n 的最大 2 的幂次,t 为余数。
推导逻辑:
- 在第一轮过程中,我们需要先剔除
t个人,使得剩余人数恰好变为2^k。 - 因为步长是 2,剔除第 1、3、5... 个偶数位,共剔除
t人后,下一个轮到报数的人成为了新一圈的起点。 - 此时剩下的人数为
2^k,根据前文结论,在新圈中以新起点为1号位的话,幸存者是其新编号中的1。 - 回到原始编号:我们剔除了
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 开始)。
思考过程:
- 正向删除:从
0, 1, ..., n-1这n个数中,第一个被杀掉的人是索引(m - 1) % n。 - 状态转移:杀掉一人后,剩下
n-1个人。为了保持逻辑一致性,我们将下一个活着的人重新映射为新的0号位。 - 逆向还原:如果我们知道了
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++ 约瑟夫环的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













