C++怎么使用三路快排_C++优化重复元素排序【效率】

2次阅读

三路快排需用lt、gt、i三指针将区间划分为三段,避免重复元素导致退化;标准std::sort不自动启用三路分区,故手写时须显式实现该逻辑。

C++怎么使用三路快排_C++优化重复元素排序【效率】

三路快排在 c++ 中怎么写才不退化成 O(n²)

标准 std::sort 对含大量重复元素的数组不会自动切三路,它用的是 introsort(混合+快排+插入),但面对 vector<int>{1,1,1,...,2,2,2,...,3}</int> 这类数据,普通快排分区仍可能反复把相等元素扫进同一侧,导致递归深度失控。

手写三路快排的关键是:用三个指针 ltgti 把区间划为「

  • 基准选 nums[l] 或随机索引,**别用 nums[r] 且不打乱——升序数组下直接 O(n²)**
  • il+1 开始,不是 llt 初始化为 lgt 初始化为 r
  • 循环中遇到 nums[i] == pivot 时,只做 i++,不交换——这是“三路”和“双路”的本质区别
  • 递归调用时,左段是 [l, lt-1],右段是 [gt+1, r],中间 [lt, gt] 全等,不用再排
void quick3way(vector<int>& nums, int l, int r) {     if (l >= r) return;     int pivot = nums[l];     int lt = l, i = l + 1, gt = r;     while (i <= gt) {         if (nums[i] < pivot) swap(nums[lt++], nums[i++]);         else if (nums[i] > pivot) swap(nums[i], nums[gt--]);         else i++;     }     quick3way(nums, l, lt - 1);     quick3way(nums, gt + 1, r); }

std::sort 能不能直接利用三路逻辑

不能。C++ 标准库不暴露三路接口std::sort 的比较器只返回 true/false,无法表达「小于/等于/大于」三态。你传进去的 operatorLambda 只能做二元判断,底层没法据此合并相等块。

如果你只是想省事又需要处理重复元素,更现实的做法是:先用 std::sort 排好,再用 std::unique 去重(或配合 erase),但注意这会改变原顺序且不压缩中间段——它只是把重复项挪到末尾。

立即学习C++免费学习笔记(深入)”;

  • 真正要保序压缩重复段,得自己遍历+双指针,比如用 std::remove_duplicates(C++20)或手写 std::unique_copy
  • std::stable_sort 不解决重复问题,它只保证相等元素相对位置不变,性能还更差
  • std::vector<String></string> 这类类型,三路快排收益有限——字符串比较本身开销大,分支预测失效比分区不均影响更大

什么时候三路快排反而更慢

当重复元素占比低于 ~15%,或者数据规模小于 100 时,三路快排的额外指针管理和分支判断会拖慢速度,不如标准 std::sort 的高度优化汇编实现。

还有两个隐蔽坑:

  • std::list 或其他非随机访问容器,三路快排无法高效 swap 中间元素,必须转成 vector 再排,内存和时间成本翻倍
  • 自定义类型若没有 operator 但有 <code>operator==,三路逻辑里容易误用 == 替代比较,导致分区错乱——三路依赖的是严格全序,不是等价关系
  • 线程环境下,手写递归分治若没加深限制(如改用迭代+显式栈),可能触发 stack overflow,而 std::sort 内部做了深度控制

替代方案:partial_sort + unique 组合够不够用

够用,但要看场景。如果你只需要前 k 个最小值且允许重复,std::partial_sort 比完整三路快排快得多;如果还要去重后再取 top-k,就先 std::sort + std::unique,再 std::make_heap 做 top-k。

但注意:std::unique 不是删除函数,它只把重复项移到末尾并返回新逻辑长度,你得手动 erase 才真正缩容:

auto last = std::unique(v.begin(), v.end()); v.erase(last, v.end());

这个组合在工程中更稳——没栈溢出风险,兼容所有迭代器类型,且编译器对 STL 函数内联和向量化更友好。三路快排只在你知道数据结构、重复率极高、且 profiling 确认瓶颈在排序时才值得投入。

真正难的不是写对三路分区,而是判断当前数据到底适不适合它——测一遍 std::sort 和你手写的耗时差,比看理论复杂度管用得多。

text=ZqhQzanResources