CPH-第三章 排序

排序是最高效的预处理手段之一,而有序性可以支撑对数时间的二分查找与拓展应用。

3.1 排序理论

基础问题与意义

  • 排序的核心目标是将任意数组按递增顺序排列。
  • 大量算法以排序为前置步骤
    • 例如判断数组是否存在重复元素
    • 统计最高频元素等,
  • 高效的通用排序算法时间复杂度为 \(O(n \log n)\),多数以排序为子步骤的算法也落在这个复杂度量级。

\(O(n^2)\) 排序算法

  • 代表:冒泡排序。通过 n 轮遍历,每轮扫描数组并交换相邻的逆序元素,每轮结束后当前最大元素会 “冒泡” 到数组末尾的正确位置。
  • 逆序对(Inversion):定义为满足 \(i<j\) 但 array[i] > array[j] 的元素对,是衡量数组无序程度的核心指标。完全逆序的数组共有 \(\frac{n(n-1)}{2}\) 个逆序对。
  • 下界结论:仅通过交换相邻元素的排序算法,时间复杂度下界为 \(O(n^2)\)。因为每次相邻交换最多消除 1 个逆序对,最坏情况需要消除 \(O(n^2)\) 个逆序对。

\(O(n \log n)\) 排序算法

  • 代表:归并排序(Merge Sort),典型的分治算法。
  • 核心流程:将数组从中点拆分为左右两半,递归排序两个子数组,再将两个有序子数组合并为一个完整的有序数组。
  • 复杂度分析:递归共 \(O(\log n)\) 层,每层合并的总耗时为 \(O(n)\),因此总时间复杂度为 \(O(n \log n)\)。

比较排序的下界

  • 结论:所有基于元素两两比较的通用排序算法,最坏情况下时间复杂度至少为 \(O(n \log n)\)。
  • 证明思路:将排序过程视为一棵决策树,每次比较产生左右两个分支,最终叶子节点对应 \(n!\) 种排列结果,因此树的高度至少为 \(\log_2(n!)\),近似等于 \(n \log n\)。

计数排序(非比较排序)

  • 适用条件:数组元素均为 \(0 \sim c\) 范围内的整数,且 \(c = O(n)\)。
  • 原理:构造计数数组统计每个数值的出现次数,再根据计数结果直接构造有序数组。
  • 复杂度:\(O(n)\),突破了比较排序的下界,但仅适用于数值范围有限的整数场景。

3.2 C++ 中的排序

  • 竞赛中几乎不手写排序算法,直接使用标准库 sort 函数,其实现经过高度优化,效率与正确性都有保障。

基本用法

  • 向量升序:sort(v.begin(), v.end()),默认按从小到大排序。
  • 向量降序:sort(v.rbegin(), v.rend())。
  • 普通数组:sort(a, a + n),传入数组首尾指针。
  • 字符串:sort(s.begin(), s.end()),按字典序排列字符。

排序规则与自定义

  • 内置与复合类型

    • 数值、字符串等内置类型默认自带比较逻辑。
    • pair:优先按 first 排序,first 相等时按 second 排序。
    • tuple:依次按第 1、2、3…… 个元素逐级比较排序。
  • 自定义结构体

    • 在结构体内部重载 operator< 定义比较规则,例如先按 x 坐标排序,x 相等时再按 y 坐标排序。
  • 外部比较函数

    • 可向 sort 传入第三个参数 —— 自定义比较函数 comp,灵活定制排序规则,例如按字符串长度优先排序,长度相同时按字典序排序。

3.3 二分查找

  • 二分查找是有序数组的核心价值所在,将线性查找的 \(O(n)\) 优化到对数时间 \(O(\log n)\)。

两种经典实现

  • 边界收缩法
    • 维护搜索区间 \([a, b]\),每次取中点 k:
      • 若 array[k] == x,直接命中目标;
      • 若 array[k] > x,目标在左半区,收缩右边界;
      • 否则收缩左边界。
    • 循环直到区间为空,时间复杂度 \(O(\log n)\)。
  • 跳跃试探法
    • 从数组左端出发,初始跳跃长度为 (n/2),
    • 之后每次跳跃长度减半;
    • 只要跳跃后位置的元素不超过 x 就向前跳,最终检查落点是否为目标元素。
    • 复杂度同样为 \(O(\log n)\)。

C++ 标准库二分函数

  • 基于有序数组,三个对数时间的工具函数:

    • lower_bound:返回第一个大于等于 x 的元素的指针 / 迭代器。
    • upper_bound:返回第一个大于 x 的元素的指针 / 迭代器。
    • equal_range:返回上述两个指针组成的 pair。
  • 典型用法:快速判断元素是否存在、统计值为 x 的元素个数。

二分的拓展应用

  • 二分的本质是利用单调性缩小搜索范围,应用场景远不止元素查找。

  • 寻找最小可行解

    • 给定判定函数 ok(x),满足当 \(x < k\) 时为 false、\(x \ge k\) 时为 true,可用二分快速找到最小的合法解 k,总时间为 \(O(\log z \times 单次判定耗时)\)。
  • 寻找单峰函数峰值

    • 对于先严格递增、后严格递减的单峰函数,可通过二分找到最大值点;要求函数不能有连续相等的平台段。
posted @ 2026-10-28 15:19  LifeLotus  阅读(2)  评论(0)    收藏  举报