线性时间查找及排序的下界笔记(不完全)

来源:互联网 发布:java get post 编辑:程序博客网 时间:2024/05/18 00:50

问题阐述

在选择问题中, 我们需要输出含有n个不同整数元素的数组A中第i小的元素,其中i{1,2,...,n}. 例如, 如果n是奇数,那么第[(n+1)/2]小的元素就是A的中位数; 如果n是偶数, 我们定义(n/2)小的元素是A的中位数.
我们可以通过对数组进行排序以 O(nlogn)的时间复杂度解决选择问题(例如使用插入排序), 然后返回有序数组的第i个元素. 下文我们给出了一个复杂的分治算法, 以线性复杂度O(n)解决了这个问题,作者是Blum, Floyd, Pratt, Rivest, 和Tarjan.
problem in linear O(n) time.

算法描述

算法中的一个关键子过程是围绕一个关键元素对数组进行划分(关键元素在(Kleinberg和Tardos的著作中被称为”划分子”), 该子过程选取了数组A中的一个元素p(我们稍后对关键元素的选择进行讨论), 并对数组A进行重新排序, 使得数组Ap之前的元素都小于p, p之后的元素都大于p. 这样, 若原始数组为[3,2,5,7,6,1,8], 其中3被选为关键元素, 那么子过程可以输出重排数组[2,1,3,5,7,8,6]. 注意我们并不关心关键元素同侧的元素彼此之间的相对顺序. 如果你曾经学习过快速排序, 那么这个子过程对于你来说应该是很熟悉的了.

以关键元素作为临界点进行划分有两个原因, 一是可以降低问题的规模, 而是可以在线性时间内对其进行求解. 一个比较容易的实现方式是对数组A进行线性扫描, 并将其中的元素从两端复制到另一个数组B中. 详细的说来,分别将指针jk初始化为1n. 从i=1i=n, 如果元素A[i]小于关键元素, 那么将元素A[i]拷贝到B[j](j递增); 如果元素A[i]大于关键元素, 那么将元素A[i]拷贝到B[k](k递减). (最后关键元素拷贝到B中的唯一一个剩余位置.)

这里有一个巧妙的方式使用重复的交换对数组进行”原地”排序(即, 不必引入第二个数组B). 阅读任何一本程序设计教材中对快速排序的伪代码描述. 事实上, 正是这种原地排序使得划分成为一个如此有吸引力的子过程. 我们(不是十分确切的说)的选择算法如下.

Select(A,n,i)
(0) If n=1 返回 A[1].
(1) p = ChoosePivot(A,n)
(2) B = Partition(A,n,p)
(3) 假设pB中的第j个元素(即A中的第j个元素). 令”B的前半部分”代表其前j1个元素, “B的后半部分”代表其后nj个元素
(3a) 如果 i=j, 返回p.
(3b) 如果 i<j, 返回Select(B的前半部分, j, i).
(3c) 否则返回 Select(B的后半部分”, nj, ij).

确保你理解了递归调用背后的合理性(比如思考一下何时i=j+1).无论如何应用ChoosePivot的子过程, 这个算法都可以正确地解决这个选择问题; 我们可以正式地对此进行证明, 直接的对数组长度n进行归纳, 如下. 奠基部分n=1很清晰The base case n = 1 is clear (考虑i=1, 数组的唯一排列方式显然是正确的). 考虑输入n<1, 假定选择出来的关键元素p是A的第j小元素, 这时我们正在寻找A的第i小元素. 如果i=j, 那么通根据定义我们返回了正确的关键元素. 如果i<j, 那么关键元素大于我们寻找的元素. 因此, A的第j小元素也是B的第j小元素(长度为j1). 通过归纳假设, 递归调用是正确的. 相似地, 如果i<j, 那么被找到的元素大于关键元素(以及B的前半部分中的所有元素); 因此是B的后半部分中的第ij小的元素(长度为nj). 又一次地, 归纳假设证明了递归调用正确地解决了这个较小规模的子问题.

算法的运行时间依赖于ChoosePivot过程挑选出来的关键元素的质量.
例如, 如果ChoosePivot总是返回数组A的第一个元素且数组A已经是有序的, 那么每一次递归调用只能讲子问题规模减少一个数组元素. 这样看来对于这个ChoosePivot的执行, Select的最坏运行时间是$\Omega(n^2) . (你能想明白为什么吗?)

因此关键就是运用ChoosePivot使得总能返回一个很好的关键元素—-可以进行一个”平衡”的划分. 当然, 最好可能的关键元素是中位数, 这就是我们之前努力寻找的, 似乎我们陷入了SQ1复原的双重困境.

算法的中心思想就是执行ChoosePivot过程使得迅速找到一个很好的关键元素. 我们很好的关键元素将会是”中位数中的中位数”, 在如下的意义中. ChoosePivot过程输入是数组A并且逻辑上将其划分为n/5组, 其中各有5个元素(一个组中最多有5个元素). (我们使用记号x最接近且不小于x的整数, 相似地, x表示最接近且不大于x的整数.) 然后每个小组独立的排序. 例如, 使用归并排序, 因此这一共最多需要 120×n/5=O(n)次操作. 拷贝”中间的”n/5个元素(即5个元素中的中位数)到一个辅助数组M中, 然后递归调用Select找到M的中位数.ChoosePivot返回M的中位数作为关键元素.

0 0