原地重排需确保索引数组为0到n-1的排列,用visited标记避免重复处理环;若仅为按规则排序,应优先使用std::sort+自定义比较器而非索引重排。

用 std::vector 和索引数组做原地重排最安全
直接按索引拷贝到新容器最简单,但若目标数组大、内存敏感,或需原地修改(比如算法题限制空间),就得小心循环置换。关键点是:索引数组必须是 0 到 n-1 的一个排列,否则行为未定义。
常见错误现象:std::out_of_range 或静默乱序——往往因为索引越界、重复或缺失;更隐蔽的是,没标记已访问位置导致某个元素被多次移动或跳过。
- 先校验索引数组:
for (int i = 0; i = arr.size()) throw std::invalid_argument("invalid index"); - 用布尔向量
visited标记位置,避免重复处理环;每个环从首个未访问下标开始,把元素沿环“推”一圈 - 不要用
swap简单两两交换——它只适合相邻或固定对换,不适用于任意排列
std::sort 配合索引数组间接排序的适用场景
如果你实际想“按某规则排序”,而索引数组只是排序结果(比如 argsort 输出),那没必要重排原数组,改用 std::sort + 自定义比较器更清晰、稳定、且支持 std::stable_sort。
使用场景:你想让 arr 按另一个数组 keys 的大小顺序排列,此时先生成索引数组 indices(按 keys 排序后的下标),再用它重排 arr ——但更直接的做法是:
立即学习“C++免费学习笔记(深入)”;
std::vector<std::pair<T, int>> keyed; for (int i = 0; i < arr.size(); ++i) keyed.emplace_back(keys[i], i); std::sort(keyed.begin(), keyed.end()); std::vector<T> sorted_arr; for (auto& p : keyed) sorted_arr.push_back(arr[p.second]);
这样避免了手动管理环、越界、稳定性等细节,也更容易调试。
用 std::transform 实现无副作用的函数式重排
如果不需要修改原数组,只是需要一个重排后的新数组,std::transform 最简洁,语义明确,且天然避免原地操作的坑。
- 输入索引数组
perm和目标数组arr,输出新数组result - 注意迭代器类型匹配:
perm是int类型,arr是随机访问容器,所以arr[perm[i]]合法 - 性能上,现代编译器能很好优化这种遍历,比手写循环未必慢,且可读性高
示例:
std::vector<int> result(perm.size());
std::transform(perm.begin(), perm.end(), result.begin(),
[&arr](int i) { return arr[i]; });
Python 用户转 C++ 时最容易踩的坑
习惯写 arr[perm](NumPy 风格)或 arr[perm[i]] 却忘了检查 i 范围——C++ 不做运行时索引检查(除非用 at()),越界就直接 UB。
- 别依赖
operator[]的边界检查:它不检查,at()才检查但带异常开销 - 别假设
perm是size_t:它常是int,和arr.size()(size_t)混用易触发隐式转换警告或负数截断 - 别在循环里反复调用
arr.size()和perm.size():虽然开销小,但显式存成const auto n = arr.size();更安全、更易读
重排本身逻辑不复杂,真正花时间的永远是验证索引合法性、处理边界条件、以及确认是否真需要原地操作——多数情况下,生成新数组更省心。


















