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)\)。
- 维护搜索区间 \([a, b]\),每次取中点 k:
- 跳跃试探法
- 从数组左端出发,初始跳跃长度为 (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 单次判定耗时)\)。
- 给定判定函数
-
寻找单峰函数峰值
- 对于先严格递增、后严格递减的单峰函数,可通过二分找到最大值点;要求函数不能有连续相等的平台段。
浙公网安备 33010602011771号