用C语言模拟‘击鼓传花’:PTA习题8-4报数游戏两种解法详解(附完整代码)

张开发
2026/4/16 10:39:35 15 分钟阅读

分享文章

用C语言模拟‘击鼓传花’:PTA习题8-4报数游戏两种解法详解(附完整代码)
用C语言模拟‘击鼓传花’PTA习题8-4报数游戏两种解法详解附完整代码在编程学习的道路上算法题常常让人望而生畏。但如果我们换个角度把算法问题想象成生活中的游戏一切就会变得有趣起来。PTA习题8-4的报数游戏本质上就是一个经典的击鼓传花问题——一群人围成一圈按特定规则依次退出直到最后一人留下。本文将带你用C语言实现这个游戏并深入分析两种不同的解决思路。1. 问题理解与建模报数游戏约瑟夫问题的规则很简单n个人围成一圈从第一个人开始报数数到m的人退出然后从下一个人重新开始报数数到m的人再退出直到所有人都退出为止。我们需要记录每个人退出的顺序。这个问题看似简单但在编程实现时需要考虑几个关键点环形结构虽然C语言没有内置的环形数据结构但我们可以用数组配合取模运算来模拟状态标记需要跟踪哪些人已经退出哪些人仍在游戏中边界条件特别注意数组索引的处理避免越界访问#define MAXN 1000 // 假设最多1000人参与游戏 void CountOff(int n, int m, int out[]);2. 标记法实现解析第一种解法采用标记法思路直观用一个额外数组记录每个人的状态是否已退出然后循环遍历这个数组直到所有人都退出。2.1 核心代码实现void CountOff(int n, int m, int out[]) { int remaining n; // 剩余人数 int count 0; // 当前报数 int sequence 1; // 退出序号 int status[MAXN]; // 状态数组 // 初始化所有人都未退出 for(int i0; in; i) { status[i] 1; // 1表示在圈内 } while(remaining 0) { for(int i0; in; i) { if(status[i]) { // 如果人在圈内 count; if(count m) { // 报到m的人退出 status[i] 0; // 标记为已退出 out[i] sequence; count 0; remaining--; } } } } }2.2 关键点分析状态数组status数组记录每个人是否仍在游戏中1表示在0表示已退出双重循环外层while控制游戏是否继续内层for遍历所有人计数重置每当有人退出时将count重置为0从下一个人重新开始报数注意这种方法在n较大时效率不高因为需要多次完整遍历数组3. 计数法实现解析第二种解法更为巧妙我们称之为计数法。它不需要额外的状态数组而是直接在输出数组上进行操作通过计数来确定下一个退出的人。3.1 核心代码实现void CountOff(int n, int m, int out[]) { int count 0; // 当前报数 int sequence 1; // 退出序号 int pos 0; // 当前位置 // 初始化所有人都未分配退出序号 for(int i0; in; i) { out[i] 0; } while(sequence n) { if(out[pos] 0) { // 如果该位置的人未退出 count; if(count m) { // 报到m的人退出 out[pos] sequence; count 0; } } pos (pos 1) % n; // 环形移动 } }3.2 关键点分析就地操作直接在out数组上标记节省了额外空间环形移动pos (pos 1) % n确保索引在0到n-1之间循环单次遍历不需要完整遍历数组效率更高提示这种方法在处理大规模数据时性能更优但逻辑稍复杂4. 两种方法的对比与选择为了更清晰地理解两种方法的差异我们通过下表进行对比特性标记法计数法空间复杂度O(n)需要额外状态数组O(1)就地操作时间复杂度O(n×m)最坏情况O(n×m)但实际更快代码复杂度简单直观较复杂需要理解环形移动适用场景n较小代码可读性优先n较大性能优先在实际编程练习中建议初学者先掌握标记法理解问题本质后再尝试计数法。对于PTA这类在线评测系统两种方法都能通过测试但计数法在大数据量时表现更优。5. 常见错误与调试技巧在实现这个算法时有几个常见的陷阱需要注意数组越界忘记对索引取模是最常见的错误// 错误示例 pos; // 可能越界 // 正确做法 pos (pos 1) % n;循环条件错误使用错误的终止条件可能导致无限循环// 错误示例 while(1) { /* 可能无法退出 */ } // 正确做法 while(sequence n) { /* 明确终止条件 */ }计数逻辑错误只在有效位置未退出的人才增加计数// 错误示例 count; // 没有检查是否已退出 // 正确做法 if(out[pos] 0) count;调试时可以添加临时打印语句观察程序运行时的变量变化printf(pos%d, count%d, sequence%d\n, pos, count, sequence);6. 完整可运行示例下面提供一个完整的程序示例包含主函数和测试用例#include stdio.h #define MAXN 1000 void CountOff(int n, int m, int out[]) { // 这里插入你选择的方法实现 } int main() { int n, m; int out[MAXN]; printf(输入总人数n和报数m); scanf(%d %d, n, m); CountOff(n, m, out); printf(退出顺序\n); for(int i0; in; i) { printf(%d , out[i]); } printf(\n); return 0; }测试用例示例输入7 3 输出3 6 2 7 5 1 4这个输出表示第3个人第一个退出第6个人第二个退出依此类推最后留下的是第4个人。7. 算法优化思路对于特别大的n和m我们可以进一步优化算法数学方法约瑟夫问题有数学公式解可以在O(n)时间内解决递归解法利用递归关系式代码更简洁但可能有栈溢出风险链表实现更适合动态删除的场景但C语言实现较复杂对于PTA习题上述两种方法已经足够。但在实际面试或竞赛中了解这些优化思路会很有帮助。8. 实际应用场景虽然这看起来只是一个练习题但类似的思想在很多实际场景中都有应用操作系统进程调度算法游戏开发回合制游戏的玩家顺序网络安全密钥轮换机制理解这个简单的报数游戏将为你解决更复杂的循环处理问题打下坚实基础。

更多文章