原位循环移动不宜直接用 std::rotate,因其要求双向迭代器,且在 k 与 n 不互质时调试困难;推荐三步翻转法,通过三次翻转实现可控、无依赖的 O(n) 时间、O(1) 空间原位移动。

原位循环移动为什么不能直接用 std::rotate
很多开发者第一反应是调用 std::rotate,它确实原位、O(n) 时间、O(1) 空间。但实际中容易踩坑:std::rotate 要求迭代器支持双向遍历(bidirectional_iterator),而某些自定义容器或 C 风格数组指针传入时可能触发编译错误;更隐蔽的是,当移动步数 k 与数组长度 n 不互质时,std::rotate 内部的环状置换逻辑虽正确,但调试时难以验证中间状态——尤其在嵌入式或无 STL 环境下,你根本没法依赖它。
三步翻转法:手动实现可控、无依赖
核心思想是利用「翻转」操作的可逆性:左移 k 步等价于「翻转前 n−k 个 + 翻转后 k 个 + 全局翻转」;右移 k 步则是「翻转前 k 个 + 翻转后 n−k 个 + 全局翻转」。整个过程只需一个临时变量,不申请额外内存,且每元素最多被访问 2 次。
实操建议:
- 先对
k取模:k = k % n,避免无效整圈移动 - 右移
k的标准写法(适用于int arr[]或std::vector<T>::data()):void rotate_right(int* arr, int n, int k) { if (n <= 1 || k == 0) return; k %= n; reverse(arr, arr + k); reverse(arr + k, arr + n); reverse(arr, arr + n); } -
reverse必须手写(或用std::reverse,但注意其要求同样为双向迭代器);若处理非 POD 类型,确保移动赋值/交换安全
环状置换法:节省一次翻转,但索引易错
当明确要最小化内存访问次数(如大数组+缓存敏感场景),可用环状置换:把数组看作若干不相交的循环,每个循环内元素依次前移。总循环数等于 gcd(n, k),每个循环长度为 n / gcd(n, k)。
立即学习“C++免费学习笔记(深入)”;
关键陷阱:
- 起始点必须从
0开始,且仅遍历gcd(n,k)个起点,否则会重复移动 - 内层循环中,用
(i + k) % n计算下一位置,但若k为负或未取模,结果溢出导致越界 - 示例(右移
k):void rotate_right_cycle(int* arr, int n, int k) { if (n <= 1 || k == 0) return; k = ((k % n) + n) % n; // 处理负 k int cycles = gcd(n, k); for (int start = 0; start < cycles; ++start) { int tmp = arr[start]; int i = start; do { i = (i + k) % n; std::swap(arr[i], tmp); } while (i != start); } }
移动方向与符号约定必须统一
“循环移动”本身不定义方向,但代码里 k > 0 到底表示左移还是右移,不同项目甚至不同函数签名常不一致。比如 std::rotate(first, middle, last) 中 middle 指向新首元素,隐含右移语义;而某些嵌入式 API 把正 k 定义为左移。一旦混用,数据就错乱。
建议做法:
- 函数名显式标注方向:
rotate_left_inplace、rotate_right_inplace,绝不使用模糊的rotate_array - 参数注释强制说明:
// k: number of positions to shift right - 单元测试必须覆盖
k = 0、k = n、k > n、k < 0四种情况
最麻烦的其实是跨平台移植:x86 上整数除法快,gcd 计算开销小;ARM Cortex-M3 没硬件除法,gcd 可能比三次翻转还慢。这时候三步翻转反而更稳。


















