C语言学习笔记20260701-环形数组移动与随机数生成
1. 项目背景
本题是一个经典的模拟算法 问题。我们需要模拟一个角色(旺仔哥哥)在一个由 NNN 个小朋友组成的圆圈中移动的过程。
- 输入 :一组代表步数的数字序列、移动次数 MMM、移动方向(0为逆时针,1为顺时针)
- 输出:最终停留位置的小朋友编号(1~N)
- 难点:处理圆环的边界溢出(取模运算)以及负数索引的处理
2. 核心知识点解析
2.1 环形移动的数学模型
在计算机中,数组是线性的(0 到 N-1),而题目是环形的。我们需要利用**取模运算(%)**来实现"转圈"的效果。
假设当前下标为 cur,步数为 step,数组长度为 sz:
顺时针移动(下标增加)
直接相加并取模:
c
cur = (cur + step) % sz;
逆时针移动(下标减小)
C语言中负数取模可能得到负数(例如 -1 % 5 结果为 -1),这会导致数组越界。必须加上一个周期 sz 再取模,将其转化为正数索引:
c
// 标准公式:(当前 - 步数 + 总长) % 总长
cur = (cur - step % sz + sz) % sz;
2.2 常见陷阱与避坑指南
**sizeof**** 的误区**sizeof(arr)返回的是字节数 。对于int arr[10],结果是 40(假设 int 占 4 字节)- 正确获取元素个数 :
int sz = sizeof(arr) / sizeof(arr[0]);
- 状态保持
- 在
for循环模拟移动时,千万不要在循环内部重置当前位置 cur必须在循环外初始化,并在循环内不断更新,代表"上一步结束时的位置"
- 在
- 随机数种子
- 使用
srand((unsigned int)time(NULL))确保每次运行程序生成的随机序列不同
- 使用
3. 完整修正代码
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h> // rand, srand
#include <time.h> // time
/**
* @brief 计算旺仔哥哥最终停留的位置
* @param arr 小朋友衣服上的数字数组
* @param sz 数组长度(小朋友人数)
* @param m 移动次数
* @param dir 移动方向 (0: 逆时针, 1: 顺时针)
* @return 最终停留的小朋友编号 (1 ~ sz)
*/
int stopAtWho(int arr[], int sz, int m, int dir) {
// 初始站在第1个小朋友旁边,对应数组下标 0
int cur = 0;
for (int i = 0; i < m; i++)
{
// 1. 获取当前站立位置的数字作为本次移动的步数
int step = arr[cur];
// 2. 根据方向计算新位置
if (dir == 1)
{
// 顺时针:下标增加
// 注意:step 可能很大,先 % sz 优化性能(可选)
cur = (cur + step) % sz;
} else
{
// 逆时针:下标减小
// 核心技巧:+ sz 防止负数取模出错
cur = (cur - (step % sz) + sz) % sz;
}
}
// 题目要求返回编号(1开始),数组下标是0开始,所以 +1
return cur + 1;
}
int main()
{
int arr[10] = { 0 };
// --- 1. 随机生成数据 ---
srand((unsigned int)time(NULL));
printf("生成的随机数字序列: ");
for (int i = 0; i < 10; i++)
{
// 生成 1~100 之间的随机数
arr[i] = rand() % 100 + 1;
printf("%d ", arr[i]);
}
printf("\n");
// --- 2. 获取用户输入 ---
int m = 0, n = 0;
// 计算数组实际元素个数,而不是字节数
int sz = sizeof(arr) / sizeof(arr[0]);
printf("请输入移动次数 m 和方向 n (0逆/1顺): ");
if (scanf("%d %d", &m, &n) != 2)
{
printf("输入格式错误\n");
return 1;
}
// --- 3. 调用函数并输出结果 ---
// 将方向参数 n 传入函数
int result = stopAtWho(arr, sz, m, n);
printf("旺仔哥哥最后停在第 %d 号小朋友旁边\n", result);
return 0;
}
4. 学习总结
通过这个练习,我们掌握了三个重要的编程技能:
- 环形缓冲区思想:利用取模运算将线性数组首尾相接
- 防御性编程 :在处理减法取模时,习惯性加上模数(
+ sz)以防止负数溢出 - 调试思维 :学会区分"循环计数器"(
i)和"业务状态变量"(cur),这是新手最容易犯的逻辑错误之一
5. 常见问题解答
Q1: 为什么需要 (step % sz) 而不是直接用 step?
A : 当步数 step 大于数组长度 sz 时,step % sz 可以避免不必要的完整循环,提高效率。例如在长度为5的数组中走7步,等价于走2步。
Q2: 为什么逆时针移动要加 sz?
A : C语言中负数取模的结果可能是负数(如 -2 % 5 = -2),而数组下标不能为负。通过 + sz 将结果调整到 [0, sz-1] 范围内。
Q3: 如何验证代码正确性?
A: 可以用简单的测试用例手动验证:
- 数组
[2,1,4,5,2,3],m=3,顺时针 - 初始位置:1号(下标0)
- 第1步:走2步 → 3号(下标2)
- 第2步:走4步 → 1号(下标0)
- 第3步:走2步 → 3号(下标2)
- 结果:3号小朋友