发布于2026-07-14 阅读(0)
扫一扫,手机访问
不能直接用std::rotate是因为自定义类型可能不满足可移动/可复制要求,或某些 STL 实现未优化为环形置换;此时需手写环形置换法,用gcd(n,k)确定循环组数,仅靠三个指针和一个临时变量完成原位旋转。

先说一个核心判断:很多人第一反应是调用 std::rotate——它确实原位、标准、简洁。但实际项目中常遇到两种情况,让它直接“失效”:自定义类型不满足可移动/可复制要求(比如含非 trivial 析构或禁用拷贝的类),或者编译器对 std::rotate 的实现未做优化(某些嵌入式 STL 版本仍用三段拷贝而非环形置换)。这时候必须手写逻辑,而且不能依赖 std::move 或临时对象。
std::rotate 就完事?多数人第一反应是调用 std::rotate —— 它确实原位、标准、简洁。但实际项目中常遇到两种情况让它失效:自定义类型不满足可移动/可复制要求(比如含非 trivial 析构或禁用拷贝的类),或者编译器对 std::rotate 的实现未做优化(某些嵌入式 STL 版本仍用三段拷贝而非环形置换)。此时必须手写逻辑,且不能依赖 std::move 或临时对象。
核心思路是把数组看作若干不相交的循环链,每个元素按步长 k 跳转,直到回到起点。关键在于计算循环节个数:gcd(n, k),它决定了要启动几个独立循环。每轮循环内只用一个临时变量暂存值,其余靠赋值链完成。
实操要点:
k 取模:k = k % n,避免无效整圈移动std::gcd(C++17+)或手写欧几里得算法求循环组数gcd(n,k) 次,每次从索引 i 开始;内层循环直到回到 in == 0 或 k == 0 直接返回,避免除零或空操作示例(右移 2 位):
void rotate_right(int* arr, int n, int k) {
if (n <= 1 || k == 0) return;
k = k % n;
int cycles = std::gcd(n, k);
for (int i = 0; i < cycles; ++i) {
int temp = arr[i];
int j = i;
do {
int next = (j + k) % n;
std::swap(arr[j], arr[next]); // 或直接赋值:arr[j] = arr[next]
j = next;
} while (j != i);
arr[i] = temp; // 补回起点
}
}
比环形置换更少出错,原理简单:右移 k 等价于「整体反转 → 前 k 反转 → 后 n-k 反转」。所有操作都是对半交换,无取模、无循环计数,CPU 预取友好,尤其适合大数组。
使用场景与细节:
swap 是 noexcept 且廉价k 必须先规约:k = k % n,否则反转区间越界n/2,而环形置换最坏也是 n 次赋值,性能接近reverse(arr, arr + k) 不会越界只要 k < n)简明实现:
void rotate_right(int* arr, int n, int k) {
if (n <= 1 || k == 0) return;
k = k % n;
std::reverse(arr, arr + n);
std::reverse(arr, arr + k);
std::reverse(arr + k, arr + n);
}
左移 k 等价于右移 n - k,但直接算 n - k 有风险:若 k > n 且未先取模,n - k 会变成负数。正确做法始终先做 k %= n,再根据方向决定用 k 还是 n - k。
容易踩的坑:
k = n - k % n —— 应该是 k = (n - (k % n)) % n,但更安全的是统一规约后分支处理sizeof 算数组长度?传参只能拿到指针,n 必须显式传入RandomAccessIterator,否则 + 和 - 不合法真正难的不是算法本身,而是让这段代码在 constexpr 上下文、无异常保证、或内存受限环境下依然成立——这时候连 std::gcd 都得自己写,且不能递归。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8