相关笔记

概览

本节介绍 RANDOMIZED-SELECT 算法(也称 Quickselect),它是基于快速排序思想的选择算法。与快速排序递归处理分区两侧不同,RANDOMIZED-SELECT 每次只递归处理一侧,因此期望运行时间为 ====,而非快速排序的 。该算法由 Hoare 于1961年与快速排序同时发明。

要点列表:

  • RANDOMIZED-SELECT 的期望运行时间为 ====,最坏情况为
  • 算法核心:利用 7.1 快速排序的描述 的 RANDOMIZED-PARTITION 划分数组,然后只递归处理包含目标元素的一侧
  • 期望线性时间的证明依赖于指示器随机变量和”有益划分”(helpful partitioning)的概念
  • 与排序后取第 小元素相比(),Quickselect 在只需要单个序统计量时更高效

知识结构总览

flowchart TD
    A["9.2 期望线性时间选择 (RANDOMIZED-SELECT)"] --> B["算法流程"]
    A --> C["与快速排序的对比"]
    A --> D["期望运行时间分析"]
    A --> E["算法特性"]

    B --> B1["基线条件: p == r"]
    B --> B2["RANDOMIZED-PARTITION"]
    B --> B3["判断 pivot 是否为目标"]
    B --> B4["递归处理一侧"]

    C --> C1["两侧递归 vs 单侧递归"]
    C --> C2["Θ(n lg n) vs Θ(n)"]

    D --> D1["引理9.1: 有益划分概率 ≥ 1/2"]
    D --> D2["定理9.2: 期望 Θ(n)"]
    D --> D3["代数法证明"]

    E --> E1["最坏情况 Θ(n²)"]
    E --> E2["随机化: 无特定输入触发最坏情况"]
    E --> E3["原地操作"]

核心思想

核心思路

RANDOMIZED-SELECT 的关键洞察在于:选择问题只需要找到一个元素,不需要对两侧都排序。快速排序每次划分后递归处理两侧,产生 的期望时间;而选择算法每次只需递归处理一侧,期望递归深度为常数级,因此总期望时间为

直觉类比:假设你要在一本1000页的字典中找第500个词。快速排序相当于把字典完全整理好(两侧都排),而选择算法相当于每次翻到随机一页,判断目标在左半部分还是右半部分,然后只翻那一半——平均只需翻几次就能找到。

算法执行流程

  1. 基线检查:若 p == r,子数组只有一个元素,直接返回 A[p]
  2. 随机划分:调用 RANDOMIZED-PARTITION,随机选择主元 q 并划分数组
  3. 计算排名:k = q - p + 1,确定主元在当前子数组中的排名
  4. 判断目标位置:若 i == k,主元恰好是目标,返回 A[q]
  5. 递归查找:若 i < k 则在左半部分递归查找;若 i > k 则在右半部分递归查找(i 调整为 i - k)
flowchart TD
    A["开始 RANDOMIZED-SELECT"] --> B{"p == r?"}
    B -- "是" --> C["返回 A[p]"]
    B -- "否" --> D["q = RANDOMIZED-PARTITION"]
    D --> E["k = q - p + 1"]
    E --> F{"i == k?"}
    F -- "是" --> G["返回 A[q]"]
    F -- "否" --> H{"i < k?"}
    H -- "是" --> I["递归: 左半部分 A[p..q-1], i 不变"]
    H -- "否" --> J["递归: 右半部分 A[q+1..r], i 调整为 i-k"]

RANDOMIZED-SELECT 伪代码

RANDOMIZED-SELECT(A, p, r, i)
1  if p == r
2      return A[p]                      // 基线条件:只有一个元素
3  q = RANDOMIZED-PARTITION(A, p, r)   // 随机选择枢轴并划分
4  k = q - p + 1                       // 枢轴是第 k 小元素
5  if i == k
6      return A[q]                     // 枢轴恰好是目标
7  elseif i < k
8      return RANDOMIZED-SELECT(A, p, q - 1, i)       // 目标在左侧
9  else return RANDOMIZED-SELECT(A, q + 1, r, i - k)  // 目标在右侧

RANDOMIZED-SELECT

输入: 数组 个元素),整数 输出: 中第 小的元素

算法步骤:

  1. 基线条件:,子数组只有一个元素,直接返回
  2. 划分: 调用 RANDOMIZED-PARTITION(A, p, r),随机选择枢轴并将数组划分为 ,使得
  3. 判断: 计算 (枢轴在当前子数组中的排名)
    • :枢轴就是第 小元素,返回
    • :目标在左侧子数组,递归搜索 中的第
    • :目标在右侧子数组,递归搜索 中的第

逐行执行逻辑详解

第1-2行(基线条件):

  • 时,子数组 只包含一个元素
  • 此时 ,所以 必须等于
  • 直接返回 即可

第3行(随机划分):

  • 调用 RANDOMIZED-PARTITION(见 7.1 快速排序的描述 第7.3节)
  • 该过程随机选择一个元素作为枢轴,将数组划分为两部分
  • 返回值 是枢轴的最终位置
  • 划分后:

第4行(计算排名):

  • 表示枢轴 在子数组 中是第 小的元素
  • 因为 共有 个元素,且都

第5-6行(命中检查):

  • ,说明我们要找的第 小元素恰好就是枢轴
  • 直接返回

第7-8行(左侧递归):

  • ,第 小元素在
  • 递归调用 RANDOMIZED-SELECT(A, p, q-1, i)
  • 注意: 不变,因为我们在更小的子数组中仍然找第

第9行(右侧递归):

  • ,第 小元素在
  • 递归调用 `RANDOMIZED-SELECT(A, q+1, r, i-k)$
  • 关键: 变为 ,因为左侧有 个比目标更小的元素已被”跳过”

与快速排序的对比

RANDOMIZED-SELECT vs RANDOMIZED-QUICKSORT

比较维度RANDOMIZED-SELECTRANDOMIZED-QUICKSORT
递归策略只递归处理一侧递归处理两侧
期望时间
最坏时间
输出 小元素完整排序数组
划分过程相同(RANDOMIZED-PARTITION)相同(RANDOMIZED-PARTITION)

为什么只递归一侧就能从 降到

快速排序的递归树深度为 ,每层总工作量为 ,因此总时间为 。而 RANDOMIZED-SELECT 的递归树是一条单链(每次只走一侧),虽然链的期望长度不是 ,但由于每次划分后子问题期望缩小一个常数比例,总工作量形成一个几何递减序列,其和为


期望运行时间分析

直觉理解

直觉

假设每次随机选择的枢轴都落在排序后数组的”中间一半”(第二和第三四分位数之间)。那么每次划分后,至少有 的元素被排除在后续递归之外,最多只有 的元素仍在考虑中。此时递归关系为 ,由主定理情况3可得

实际上,枢轴不一定每次都落在中间一半。但由于枢轴是随机选择的,落在中间一半的概率约为 。因此期望约每两次划分就能获得一次”有益划分”,期望运行时间仍为

有益划分(Helpful Partitioning)

有益划分

定义第 次划分后仍在考虑中的元素集合为 ,其中 为全部元素。若一次划分满足

则称该划分为有益划分(helpful partitioning)。

引理 9.1:有益划分的概率

引理 9.1(Lemma 9.1)

一次划分是有益划分的概率至少为

证明:

【充分性(中间一半 有益划分)】 枢轴落入中间一半时,至少 个元素被排除

定义 元素子数组的”中间一半”(middle half)为:去掉最小的 个元素和最大的 个元素后剩余的元素集合。

第一步:证明枢轴落入中间一半 ⇒ 有益划分。

无论枢轴落在哪个位置,划分后要么所有大于枢轴的元素、要么所有小于枢轴的元素(连同枢轴本身)将不再被考虑。若枢轴落入中间一半,则至少有 个小于枢轴的元素或 个大于枢轴的元素,加上枢轴本身,至少有 个元素不再被考虑。

因此剩余元素至多为:

所以划分是有益的。

【必要性(概率下界 )】 计算枢轴不在中间一半的概率上界

第二步:证明枢轴落入中间一半的概率至少为

枢轴落入中间一半的概率为:

因此:

定理 9.2:期望运行时间

定理 9.2(Theorem 9.2)

RANDOMIZED-SELECT 在 个不同元素的输入数组上的期望运行时间

证明:

【代分组(按有益划分分代)】 将划分序列按有益划分分组,每代集合大小至多乘以

将所有划分按”有益划分”分组为(generation)。设 为有益划分的索引序列,其中 (将初始状态视为一次”虚拟”有益划分)。

定义第 代的元素集合大小为 ,其中 为原始问题规模。由于每次有益划分后集合大小至多为原来的

经过 次有益划分后,只剩下一个元素。

【几何分布(期望代内划分次数 )】 有益划分概率 ,期望等待次数

定义随机变量 ,即第 代中的划分次数。由引理 9.1,每次划分是有益划分的概率至少为 ,因此 服从几何分布(或被其控制),期望值:

比较次数上界: 次划分将枢轴与 个其他元素比较,因此比较次数少于 。第 代中的集合大小至多为 ,该代有 次划分,因此第 代的总比较次数少于

【期望线性性质 + 无穷等比级数】 利用 求和

总比较次数期望值:

因此期望比较次数为 。又因为第一次 RANDOMIZED-PARTITION 就需要检查所有 个元素,下界为

结论: RANDOMIZED-SELECT 的期望运行时间为

最坏情况分析

最坏情况

RANDOMIZED-SELECT 的最坏情况发生在每次划分都极不走运——枢轴总是当前子数组的最大或最小元素。此时每次递归只排除一个元素(枢轴本身),递归关系为:

解为

但注意: 由于算法是随机化的,没有特定输入会始终触发最坏情况行为。对任何固定输入,期望运行时间都是


补充理解与拓展

Quickselect 的工程应用与历史

Quickselect(RANDOMIZED-SELECT)由 Tony Hoare 于1961年在其经典论文 “Quicksort”(Computer Journal, Vol. 5, pp. 10-16)中与快速排序同时提出,也称为 “Hoare’s selection algorithm”。该算法是选择问题在实际工程中最广泛使用的解决方案。

主要工程应用:

应用场景实现方式说明
C++ STLstd::nth_element标准库直接提供,通常基于 Introselect(Quickselect + BFPRT 的混合策略)
Pythonstatistics.median对小数组使用排序,对大数组可能使用 Quickselect 变体
数据库引擎MEDIAN 聚合函数PostgreSQL、MySQL 等使用 Quickselect 或其变体计算中位数
流处理近似中位数算法Greenwald-Khanna (2001) 提出的单遍近似中位数算法,适用于无法存储全部数据的数据流场景

Quickselect vs 排序后取第 个:

  • 排序需要 ,Quickselect 期望
  • 当只需要一个序统计量时,Quickselect 更高效
  • 当需要多个序统计量或完整排序时,直接排序更合适

来源:Hoare, C.A.R., “Quicksort”, Computer Journal, 1962; Musser, D.R., “Introspective Sorting and Selection Algorithms”, Software: Practice and Experience, 1997; Greenwald & Khanna, “Space-efficient online computation of quantile summaries”, SIGMOD 2001

指示器随机变量技术——期望分析的核心工具

定理 9.2 的证明使用了与 5.2 指示器随机变量7.1 快速排序的描述 第7.4节相同的分析技术——指示器随机变量(indicator random variable)。该技术是分析随机化算法期望运行时间的核心工具。

技术要点:

  1. 将随机事件映射为指示器随机变量
  2. 利用期望的线性性质:
  3. 每个指示器的期望等于对应事件发生的概率:

在 RANDOMIZED-SELECT 分析中的具体应用:

  • 定义 为第 代中的划分次数(几何分布随机变量)
  • 利用 (因为有益划分概率
  • 将总比较次数表示为 ,利用期望线性性求和

这一技术也广泛应用于:

  • 快速排序期望比较次数分析(第7.4节)
  • 散列表期望查找时间分析(第11章)
  • 跳表期望搜索时间分析

来源:Cormen et al., Introduction to Algorithms, 4th ed., Section 5.2; Motwani & Raghavan, Randomized Algorithms, Cambridge University Press, 1995


易混淆点与辨析

误区:RANDOMIZED-SELECT 的最坏情况 意味着它不可靠

错误理解: “RANDOMIZED-SELECT 最坏情况是 ,和直接排序差不多,还不如排序后取第 个”

正确理解: RANDOMIZED-SELECT 是随机化算法,最坏情况 只在极端不走运时发生,概率极低。对任何固定输入,期望运行时间都是 。不存在特定输入能始终触发最坏情况。

对比: 非随机化的选择算法(如每次选第一个元素作枢轴)确实存在特定输入(如已排序数组)会始终触发最坏情况。随机化的优势在于将最坏情况从”输入依赖”变为”概率事件”

如果需要确定性的最坏情况 保证,应使用 9.3 最坏情况线性时间选择 中的 SELECT 算法。

误区:RANDOMIZED-SELECT 递归调用可能传入空数组

错误理解: “当 时递归调用 RANDOMIZED-SELECT(A, p, q-1, i),如果 则传入空数组,会出错”

正确理解: RANDOMIZED-SELECT 永远不会对空数组进行递归调用。

证明:,则 。此时第5行检查 ,直接返回 ,不会进入第7-9行的递归分支。类似地,若 ,则 ,此时 ,不会进入 的分支。因此递归调用的子数组始终至少包含一个元素。


习题精选

题号题目描述难度
9.2-1证明 RANDOMIZED-SELECT 永远不会对空数组进行递归调用
9.2-2写出 RANDOMIZED-SELECT 的迭代版本⭐⭐
9.2-3描述一个使 RANDOMIZED-SELECT 在数组 上产生最坏情况性能的划分序列⭐⭐
9.2-4论证 RANDOMIZED-SELECT 的期望运行时间不依赖于输入数组中元素的排列顺序⭐⭐⭐

视频学习指南

资源主题链接说明
MIT 6.006 Lecture 6Selection Sort, Quickselecthttps://www.youtube.com/watch?v=8BiVbP15G1oMIT 公开课,讲解 Quickselect 的核心思想与期望分析
Abdul BariQuick Select Algorithmhttps://www.youtube.com/watch?v=FN4wGLBiAOQ逐步动画演示 Quickselect 的执行过程,适合初学者
NeetCodeQuick Selecthttps://www.youtube.com/watch?v=XEmy13g1QxcLeetCode 实战视角,含代码实现
WilliamFisetQuickselecthttps://www.youtube.com/watch?v=u9N_aPCQDE4算法系列教程,含复杂度分析
CS DojoQuickselect in Pythonhttps://www.youtube.com/watch?v=A3Z8Lz8mxgoPython 实现,适合编程练习

教材原文

CLRS 第4版 9.2节原文

The general selection problem—finding the th order statistic for any value of —appears more difficult than the simple problem of finding a minimum. Yet, surprisingly, the asymptotic running time for both problems is the same: . This section presents a divide-and-conquer algorithm for the selection problem. The algorithm RANDOMIZED-SELECT is modeled after the quicksort algorithm of Chapter 7. Like quicksort it partitions the input array recursively. But unlike quicksort, which recursively processes both sides of the partition, RANDOMIZED-SELECT works on only one side of the partition. This difference shows up in the analysis: whereas quicksort has an expected running time of , the expected running time of RANDOMIZED-SELECT is , assuming that the elements are distinct.


参见Wiki

第09章-中位数与序统计 随机化选择