前言
本章如果要归结成一个问题的话,可以归结为选择问题,比如要从一堆数中选择最大的数,或最小的数,或第几小/大的数等。
中位数和顺序统计量
选择问题:给定一个包含n个元素的集合A和一个整数i,1<=i<=n,我们需要得到一个整数x,其中有i-1个元素小于它,即第i个顺序统计量。
- 顺序统计量:在一个n个元素组成的集合中,第i个顺序统计量是该集合中第i小的元素。
- 中位数:它所属集合的“中点元素”。如果集合元素n为奇数,则中位数为(n+1)/2处;如果n为偶数,则中位数出现在n/2(下中位数)和n/2+1(上中位数)处,一般无特殊说明,我们都取下中位数。
- 最大值:第1个顺序统计量。
- 最小值:第n个顺序统计量。
这个问题最直观的解法是通过排序+索引的方式,但排序算法有多种,且时间复杂度略高。我们需要更低时间复杂度来解决这个问题,要求线性时间,即O(n)。
最大值、最小值
同时求最大最小值,采用最直观朴素的解法就能解决。就是一个个比较,时间复杂度O(n),已经没有比这更优的了。代码如下:
/***********线性时间求最小值************/
int Minimun(int arr[], int n)
{
int nMin = arr[0];
for(int i = 1; i < n; i++)
//min
if(nMin > arr[i])
nMin = arr[i];
//max
// if (nMax < arr[i])
// nMax = arr[i];
return nMin;
}
按照上述算法,同时求最大最小值,需要2(n-1)次比较,但是换一种思路,我们没必要一个元素比较两次,而是两个元素比较一次,然后得出大小关系,在分别和最大、最小值比较,这样两个元素就只用比较3次,总共就是3/2n次。这里要分奇偶数看待,但不管奇偶,都需要3/2n次。比较次数减少了,时间也就降低了。
练习
9-1
首先,我们先将数组中的元素两两成对比较,共需n/2次比较,那么就有n/2个元素是较小的元素,然后再将这些较小的元素再次两两成对比较,又淘汰一半,重复这样的循环,每次淘汰一半元素直到只剩下1个元素,该元素就是最小元素。
- 如上图所示,经过的比较次数为S=n/2+n/4+…(n/(2^k)=1) k=lgn S=n-1次。
- 第2小的元素肯定与最小元素比较过,所以我们采取的方法是,从根结点(最小元素)开始沿着根向叶子结点开始查找等于根节点的子结点A,第二小的元素就应该在与A结点属于同一个父结点的另外一个子结点B上,将B结点上的值给予第二小元素second,这样经过以上方式最坏lgn-1次比较
1-2
即分析3/2n次比较算法。分别从n为奇偶前提出发。
- 先拿出2元素比较,表示最大,最小,即1次
- 如果是偶数, 剩下的 3(n-2)/2次比较
- 如果为奇数,剩下的 3(n-3)/2次比较,外加2次分别和最大最小比较
以期望线性时间做选择
一般选择问题:找出数组中第i大的元素。看起来比找最小值要复杂,但实际上两种问题的渐进运行时间相同,都是Θ(n)。Randomized-Select的这个算法很强悍,期望的时间复杂度就能达到O(n),但最坏情况下的时间复杂度却为O(n^2)。该算法采用的是快速排序章节中的Partition过程来得到划分的中点,如果该中点恰好等于选择的点,则即为所求,否则再在左右两个区间中用同样的方法再次寻找,伪代码如下:
RANDOMIZED-SELECT(A, p, r, i)
1 if p = r
2 then return A[p]
3 q ← RANDOMIZED-PARTITION(A, p, r)
4 k ← q - p + 1
5 if i = k ▹ the pivot value is the answer
6 then return A[q]
7 elseif i < k
8 then return RANDOMIZED-SELECT(A, p, q - 1, i)
9 else return RANDOMIZED-SELECT(A, q + 1, r, i - k)
假设程序RANDOMIZED-SELECT以相等的可能性返回任何元素作为主元,即复杂度为S=n/2+n/4+…n/(2^k),即O(n)。。具体的复杂度求法,参考书本。
练习
2-1
(略)
2-2
(略)
2-3
RANDOMIZED-SELECT(A, p, r, i)
while true
if p == r
return A[p]
q = RANDOMIZED-PARTITION(A, p, r)
k = q - p + 1
if i == k
return A[q]
if i < k
r = q
else
p = q
i = i - k
RANDOMIZED-PARTITION(A, p, r)
x = RANDOM(p - 1, r)
swap A[x] with A[r]
return PARTITION(A, p, r)
PARTITION(A, p, r)
x = A[r]
i = p
for k = p - 1 to r
if A[k] < x
i = i + 1
swap A[i] with A[k]
i = i + 1
swap A[i] with A[r]
return i
2-4
每次都选择最大元素作为划分主元,这样就有T(n)=T(0)+T(n-1)+O(n)产生最坏运行时间。
最坏限行时间的选择
Randomized_Select在最坏情况下,时间复杂度为O(n^2).现在来看下最坏情况下复杂度为O(n),Select算法。基本思想是保证对数组的划分是个好的划分,所以,需要对Partition做一点修改,具体的算法步骤如下
- 将输入数组的n个元素划分为n/5(上取整)组,每组5个元素,且至多只有一个组有剩下的n%5个元素组成。
- 寻找每个组织中中位数。首先对每组中的元素(至多为5个)进行插入排序,然后从排序后的序列中选择出中位数。
- 对第2步中找出的n/5(上取整)个中位数,递归调用SELECT以找出其中位数x。(如果是偶数去下中位数)
- 调用PARTITION过程,按照中位数x对输入数组进行划分。确定中位数x的位置k。
- 如果i=k,则返回x。否则,如果i<k,则在地区间递归调用SELECT以找出第i小的元素,若干i>k,则在高区找第(i-k)个最小元素。
如上图,即大于x的元素个数至少有:
3
(
⌈
1
2
⌈
n
5
⌉
⌉
−
2
)
≥
3
n
10
−
6
3(\lceil {1 \over 2}\lceil{n \over 5}\rceil\rceil -2) \ge {3n \over 10} -6
3(⌈21⌈5n⌉⌉−2)≥103n−6
同样,小于x的元素也至少有3n/10 - 6;因此在第5步,最坏情况有
7
n
10
+
6
{7n \over 10} + 6
107n+6 个元素递归调用Select.进而可以得到其递归式为:
运用代入法,我们可以得到其时间复杂度为O(n).
练习
3-1在算法SELECT中,输入元素被分为每组5个元素。如果它们被分为每组7个元素,该算法仍然会是线性时间吗?证明:如果分成每组3个元素,SELECT的运行时间不是线性的。
3-2分析SELECT,并证明:如果n≥140,则至少ceil(n/4)个元素大于中位数的中位数x,至少ceil(n/4)个元素小于x?
3-3 假设所有元素都是互异的,说明在最坏情况下,如何才能使快速排序的运行时间为Ο(nlgn)?
快速排序对主元的划分决定了其运行时间,如果最坏是Ο(nlgn),那么就不允许出现极端划分情况。我们可以使用Select算法的思路,选中位数作为主元的方法来避免极端情况的发生。
3-4 假设对一个含有n个元素的集合,某算法只用比较来确定第i小的元素。证明:无需另外的比较操作,它也能找到比i小的i-1个元素和比i大的n-i个元素。
因为在SELECT函数查找第i个元素时,那么就会以这个元素作为主元对整个数组进行划分,低区的i-1个元素肯定都是小于主元的,高区n-i个元素肯定都是大于主元的。
3-5 假设已经有了一个用于求解中位数的“黑箱”子程序,它在最坏情况下需要线性运行时间。写出一个能解决任意顺序统计量的选择问题的线性时间算法。
使用“黑箱”子程序找到中间值。然后利用中间值根据选择问题,依次递归查找,即可。
3-6 对一个含有n个元素的集合来说,所谓k分位数(the kth quantile),就是能把集合分成k个大小相等的集合的k-1个顺序统计量。给出一个能输出某一集合的这k-1个顺序统计量的O(nlgk)时间的算法。
理解题目的意思,找到k-1个集合,这里并没有要求集合是里面是排序的,用Select的方式,将大集合一直二分。
3-7 给出一个O(n)时间的算法,在给定一个有n个不同数字的集合S以及一个正整数k≤n后,它能确定出S中最接近其中位数的k个数
如果给出在线性时间内的算法,那么可能要用到最坏为线性时间的查找第i小元素的子程序SELECT。我们先找到这n个数的中位数,然后以此中位数为中心,左边距离中位数k/2个远的位置是这k个数的左端点,右边距离中位数k/2个远的位置是这k个数的右端点。用SELECT函数找到这三个数(中位数,左边距离中位数k/2的数,右边距离中位数k/2的数)就可以确定最接近其中位数的k个数
3-8 设 x[1…n]和Y[1…n]为两个数组,每个都包含n个已排序的数。给出一个求数组X和Y中所有2n个元素的中位数的O(lgn)时间的算法。
主要思路参考《漫画:如何找到两个数组的中位数?》
MEDIAN(X, Y, n)
if n == 1
return min(X[1], Y[1])
if X[n / 2] < Y[n / 2]
return MEDIAN(X[n / 2 + 1..n], Y[1..n / 2], n / 2)
return MEDIAN(X[1..n / 2], Y[n / 2 + 1..n], n / 2)
3-9
(略)
主要参考
《算法导论第九章中位数和顺序统计量(选择问题)》
《算法导论第九章课后答案》
《Medians and Order Statistics》