第六讲 Selection

选择问题(Selection)


\(A[1,\cdots,n]\)中的数各不相同,求其中第\(k\)大的数,即求第\(k'=n-k+1\)小的数。

基于随机化的快速选择算法(参考快速排序)

(1)随机选择一个基准\(x\)

(2)将序列划分成\(A[1,\cdots,i-1],A[i+1,\cdots,n]\)\(A[1,\cdots,i-1]<x,A[i]=x,A[i+1,\cdots,n]>x\)

(3)若\(k’<i\),则递归\(\text{Quickselect}(A[1,\cdots,i-1],k')\)

(4)若\(k'>i\),则递归\(\text{Quickselect}(A[i+1,\cdots,n],k'-i)\)

(5)若\(i=k’\),返回\(x\)

期望时间复杂度为\(O(n)\)

\(T(n)=\displaystyle \sum_{i=1}^k T(n-i)+\sum_{i=k+1}^n T(i-1)+O(n)\)

\(T(n)=\displaystyle \sum_{i=1}^{n}T(\max\{i-1, n-i\})+O(n)\) $\(\displaystyle T(n) \leqslant \frac{2}{n} \sum_{i=\lfloor n/2 \rfloor}^{n-1} T(i) + O(n)\)$

通过数学归纳法证明 \(T(n) \leqslant cn\)。 假设对于所有小于 \(n\) 的规模,都有 \(T(i) \le c i\)。其中 \(c\) 是待定常数。 $\(\displaystyle T(n) \le \frac{2c}{n} \sum_{i=\lfloor n/2 \rfloor}^{n-1} i + an\)$ $\(\displaystyle T(n) \le \frac{3cn}{4} + an + \frac{c}{2}\)$ 取 \(c > 4a\)即可。

指示变量法证明(期中不考)

假设数组排序后是:\(z_1, z_2, \dots, z_n\)。 * 目标点 \(z_k\):我们要找的那个“全场第 \(k\) 小”的数。 * 观察点 \(z_j\)\(z_i\):我们要看这一对数字是否发生了“比较”。(假设 \(i < j\)

在随机化选择中,只有当 \(z_i\)\(z_j\) 被选为基准值时,它们才可能与对方比较。

假设\(k < j\)。我们要计算 \(z_j\) 这一生由于 \(z_k\) 的存在,总共会和它左边多少个元素发生比较。考虑 \(z_k\)\(z_j\) 之间的这一排数:\(\{z_k, z_{k+1}, \dots, z_j\}\)。算法运行过程中,这组数里总会有一个数率先被随机选为 pivot。\(z_i\)\(z_j\) 会发生比较,当且仅当从这组数里挑出的第一个 pivot 是\(z_i\)\(z_j\)

  • 如果第一个挑中 \(z_i\)\(z_j\):它们会和当前区间所有数比一遍,包括对方。
  • 如果第一个挑中的是中间的数(比如 \(z_{k+1}\)):因为目标 \(z_k\) 在左边,算法会直接带队去左边,把右边的 \(z_j\) 永远留在“冷宫”里,所以 \(z_i\)\(z_j\) 再也没机会见面(比较)了。

在集合 \(\{z_k, \dots, z_j\}\) 中,元素个数是 \(j-k+1\)。从中挑中 \(z_i\)\(z_j\) 作为第一个 pivot 的概率是: $\(Pr(z_i, z_j \text{ 比较}) = \frac{2}{\text{区间总数}} = \frac{2}{j-k+1}\)$

固定 \(j\),让 \(i\)\(k\) 遍历到 \(j-1\)\(z_j\) 这一生跟左边这些数的比较次数期望: $\(E = \sum_{i=k}^{j-1} \frac{2}{j-k+1} = (j-k) \times \frac{2}{j-k+1} < 2\)$ 每个在目标右侧的数,平均只跟它左边的数比 2 次。

现在考虑 \(i\)\(z_k\) 左侧,\(j\)\(z_k\) 右侧的情况,即 \(i < k < j\)

考虑区间 \(\{z_i, z_{i+1}, \dots, z_j\}\)。同样地,必须是 \(z_i\)\(z_j\) 率先被选为 pivot,彼此之间才会发生比较。 * 如果这组数中率先选出的 pivot 是中间的那个 \(z_k\),那么 \(z_i\) 会被分到左边,\(z_j\) 会被分到右边,导致 \(z_i\)\(z_j\) 彻底分家,从此不再比较。

此时区间长度是 \(j-i+1\)。概率还是: $\(Pr(z_i, z_j \text{ 比较}) = \frac{2}{j-i+1}\)$

求和: $\(E[\text{总次数}] = \sum_{i=1}^{k} \sum_{j=k}^{n} \frac{2}{j-i+1}=O(n)\)$


快速选择算法(确定性算法)

(1)将序列分为\(n/5\)组,每组\(5\)个元素(若元素个数不为5的倍数,补充\(-\text{Inf}\)

(2)取每一组的中位数\(c_1,c_2,\cdots,c_{n/5}\)

(3)取这些元素的中位数,记为\(x\),记此元素为基准

claim:这个 \(x\) 的排名大约在整体的 \(30\%\)\(70\%\) 之间。

  • \(n/5\) 个组中,至少有一半的组( \(n/10\) 组),它们的中位数 \(c_i\) 是小于等于 \(x\) 的。而在每一个这样的组内部,又有 3 个元素是小于等于其组中位数 \(c_i\) 的。
  • 因此至少有 \(0.3n\) 的元素保证小于等于 \(x\)。同理,也至少有 \(0.3n\) 的元素大于等于 \(x\)

算法简述

(1)选取基准函数\(\text{DetPivot}(A[1,\cdots,n])\)

  • \(A\)划分为\(n/5\)个组:\(B_1,B_2,\cdots,B_{n/5}\)
  • 找到每个组的中位数:\(c_1,c_2,\cdots,c_{n/5}\)
  • \(x\gets \text{DetSelect}(C[1,\cdots,n/5],n/10)\)

(2)快速查找函数\(\text{DetSelect}(A[1,\cdots,n],k)\)(此处\(k'\)为对应的第\(k'\)小)

  • \(x\gets \text{DetPivot}(A[1,\cdots,n])\)
  • 将序列划分成\(A[1,\cdots,i-1],A[i+1,\cdots,n]\)\(A[1,\cdots,i-1]<x,A[i]=x,A[i+1,\cdots,n]>x\)
  • \(k’<i\),则递归\(\text{DetSelect}(A[1,\cdots,i-1],k')\)
  • \(k'>i\),则递归\(\text{DetSelect}(A[i+1,\cdots,n],k'-i)\)
  • \(i=k’\),返回\(x\)

时间复杂度满足\(T(n) \le T(0.2n) + T(0.7n + 9) + O(n)\)(此处的常数9被称为边界修正项,用于保证在数学归纳法中对所有规模都严格成立。)

数学推导: 假设 \(T(i) \le ci\),代入: \(T(n) \le 0.2cn + 0.7cn + \text{常数} + an\) \(T(n) \le 0.9cn + an\) 为了满足 \(0.9cn + an \le cn\),我们需要 \(an \le 0.1cn\),即 \(c \ge 10a\)

对于递推式\(T(n)=T(\alpha n)+T(\beta n)+O(n)\),当\(\alpha +\beta <1\)时,算法就是\(O(n)\)的。

  • 5 是能保证线性复杂度的最小奇数,在理论性能和实际开销之间取得了最佳平衡。

寻找最大最小问题(Finding Minimum & Maximum)

可知时间复杂度为\(O(n)\),此处研究常数影响。

一次只求解最小值或最大值,最少需要\(n-1\)次比较,这毫无疑问。

如果需要一次同时求解最小值与最大值,按照常规的方法,需要比较\(2n-2\)次,可以优化。

考虑分组竞争优化(\(3n/2\) 算法),这种方法利用了“胜者组”和“败者组”的思想。

(1)将数组元素两两成对(共 \(\lfloor n/2 \rfloor\) 对),每对内部比较 1 次。

  • 比较次数\(\lfloor n/2 \rfloor\)
  • 结果:产生了一组“局部较小值”和一组“局部较大值”。

(2)全局最小值一定在“局部较小值”中产生。

  • 比较次数\(\lceil n/2 \rceil - 1\)

(3)全局最大值一定在“局部较大值”中产生。

  • 比较次数\(\lceil n/2 \rceil - 1\)

  • 总计:约 \(\mathbf{1.5n}\) 次比较(具体为 \(3\lfloor n/2 \rfloor\) 次)。

最优性证明(状态标记法)

  • 标记系统:每个元素初始有两个标记:+(可能是最大值)和 -(可能是最小值)。总标记数为 \(2n\)
  • 终止目标:最终只能剩下一个 + 和一个 -。也就是说必须消除 \(2n-2\) 个标记。
  • 比较效率
    • 只有在两个“从未被比较过”的元素之间比较时,一次比较才能消除 2 个标记(一者不再是 Max,另一者不再是 Min)。这种情况最多发生 \(\lfloor n/2 \rfloor\) 次。
    • 随后的比较(其中至少一个元素已经失去过标记),一次比较最多只能消除 1 个标记。
    • 设总比较次数为 \(C\),则消除的标记总数满足: $\(2 \cdot \lfloor n/2 \rfloor + 1 \cdot (C - \lfloor n/2 \rfloor) \ge 2n - 2\)$ 解得 \(C \ge 2n - 2 - \lfloor n/2 \rfloor \approx 1.5n\)

为了找到第 1 大和第 2 大元素,最少需要多少次比较? * 锦标赛法: 通过树状比赛,找到最大值需要 \(n-1\) 次。在比赛路径中,直接输给最大值的元素共有 \(\lceil \log_2 n \rceil\) 个。第 2 大元素一定在这些“败者”中。总比较次数为 \(n + \lceil \log_2 n \rceil - 2\)


多路归并排序(Selection in Sorted Arrays)(期中不考)

\(m\) 个已经各自有序的数组中,找到全局第 \(k\) 小的元素,这些元素的总数为\(n\)

利用“中位数的中位数”作为基准值来快速排除不可能的搜索区间。

  • 每个数组\(A_i\)的中位数可以直接通过索引得到。从这 \(m\) 个中位数中,再找一次中位数,记为 \(x\)

  • 在每个数组 \(A_i\) 中,利用二分查找找到有多少个元素比 \(x\) 小,记为 \(k_i\)。全局比 \(x\) 小的元素总数就是 \(\sum k_i\)

  • 如果全局排名 \(k\) 落在 \(\sum k_i+1\) 范围内,说明我们要找的目标值在所有数组的“左半部分”,递归\(\text{Select}(A_i[\le k_i],k)\),否则,目标值在“右半部分”,递归\(\text{Select}(A_i[>k_i],k-\sum k_i)\)部分

时间复杂度分析:

  • \(m\) 个中位数的中位数需要 \(O(m)\)
  • \(m\) 个数组中分别做二分查找来确定 \(x\) 的排名,每次二分是 \(\log(n_i)\),总计 \(\displaystyle O(m \log \frac{n}{m})\)

迭代次数\(\displaystyle \log \frac{n}{m}\)

  • 所有数组长度的乘积在每轮迭代中以约 \((\displaystyle \frac{1}{2})^{m/2}\) 的比例下降。这意味着搜索空间收敛得极快。

总复杂度\(\displaystyle \mathbf{O(m \log^2 \frac{n}{m})}\)