在一个有 n n n个元素组成的集合中,第 i i i个顺序统计量(order statistic)是该集合中第 i i i小的元素。例如,最小值是第1个顺序统计量( i = 1 i=1 i=1),最大值是第 n n n个统计量。中位数比较特殊,当 n n n为奇数时,它是唯一的;当 n n n为偶数时,存在两个中位数,分别位于 i = n / 2 i=n/2 i=n/2和 i = n / 2 + 1 i=n/2+1 i=n/2+1处,分别称作下中位数和上中位数。为了简便起见,本书中所用的“中位数”都是指下中位数。
本章将讨论从一个由 n n n个互异的元素构成的集合中选择第 i i i个顺序统计量的问题。虽然假设集合中的元素是互异的,但实际上书中的算法也适用于集合中包含重复元素的情形。问题定义如下:
利用堆排序或归并排序,我们可以在 O ( n l g n ) O(nlgn) O(nlgn)时间内解决这个问题。本章将介绍一个更快的算法,它可以在 O ( n ) O(n) O(n)时间内完成。
下面实现了书中的伪代码和练习题,包括:
def minimum(A):
'''len(A) >= 1'''
min1 = A[0]
for i in range(1, len(A)):
if min1 > A[i]:
min1 = A[i]
return min1
def minmax(A):
'''len(A) >= 2'''
if len(A) % 2 == 0:
min1 = A[0]
max1 = A[1]
begin = 2
else:
min1 = max1 = A[0]
begin = 1
for i in range(begin, len(A), 2):
if A[i] < A[i+1]:
t_min = A[i]
t_max = A[i+1]
else:
t_min = A[i+1]
t_max = A[i]
if t_min < min1:
min1 = t_min
if t_max > max1:
max1 = t_max
return min1, max1
def minimum2(A):
'''len(A) >= 2'''
min1 = A[0]
min2 = A[1]
if min2 < min1:
min1, min2 = min2, min1
for i in range(2, len(A)):
if A[i] < min1:
min2 = min1
min1 =A[i]
elif A[i] < min2:
min2 = A[i]
return min1, min2
def test():
A = [3, 1, 4, 8, 9, 10, 2]
print(minimum(A))
print(minmax(A))
print(minimum2(A))
if __name__ == '__main__':
test()
一般的选择问题看起来要比找最小值这样的问题更难,但令人惊奇的是,这两个问题的渐近运行时间确是相同的: Θ ( n ) \Theta(n) Θ(n)。本节介绍一种解决选择问题的分治算法RANDOMIZED-SELECT,它以第7章的快速排序算法为模型。与快速排序一样,我们仍然将数组进行递归划分。但与快速排序不同的是,快速排序会递归处理划分的两边,而RANDOMIZED-SELECT只处理划分的一边。
快速排序的期望运行时间是 Θ ( n l g n ) \Theta(nlgn) Θ(nlgn),最坏运行时间是 Θ ( n 2 ) \Theta(n^2) Θ(n2);该选择算法的期望运行时间为 Θ ( n ) \Theta(n) Θ(n),最坏运行时间是 Θ ( n 2 ) \Theta(n^2) Θ(n2)。
下面实现了书中的伪代码和练习题,包括:
def partition(A, p, r):
x = A[r]
i = p - 1
for j in range(p, r):
if A[j] <= x:
i += 1
A[i], A[j] = A[j], A[i]
A[i+1], A[r] = A[r], A[i+1]
return i + 1
def randomized_partition(A, p, r):
import random
i = random.randint(p, r)
A[i], A[r] = A[r], A[i]
return partition(A, p, r)
def randomized_select(A, p, r, i):
if p == r: # i shoule be 1
return A[p]
q = randomized_partition(A, p, r)
k = q - p + 1
if i == k:
return A[q]
elif i < k:
return randomized_select(A, p, q-1, i)
else:
return randomized_select(A, q+1, r, i-k)
def randomized_select_loop(A, i):
begin, end = 0, len(A)-1
while True:
q = randomized_partition(A, begin, end)
k = q - begin + 1
if i == k:
return A[q]
elif i < k:
end = q - 1
else:
begin, i = q + 1, i - k
def test():
A = [3, 1, 4, 8, 9, 10, 2, 7, 5, 6]
for i in range(1, len(A)+1):
print(randomized_select(A, 0, len(A)-1, i), end=' ')
print()
for i in range(1, len(A)+1):
print(randomized_select_loop(A, i), end=' ')
if __name__ == '__main__':
test()
本节介绍了一个最坏情况下运行时间为 O ( n ) O(n) O(n)的选择算法SELECT。像RANDOMIZED-SELECT一样,该算法通过对数组的递归划分来找出所需元素,但是,在该算法中能够保证得到对数组的一个好的划分。该算法对来自快速排序的确定性划分算法PARTITION进行了修改,把划分的主元也作为输入参数。
算法还没看懂,代码还未完成…
上完了课,大概明白一点,然后花了三个多小时才实现…
def partition_for_select(A, p, r, pivot):
for i in range(p, r+1):
if A[i] == pivot: # 找到主元并与最后一个元素进行交换
A[i], A[r] = A[r], A[i]
break
i = p - 1
for j in range(p, r):
if A[j] <= pivot:
i += 1
A[i], A[j] = A[j], A[i]
A[i+1], A[r] = A[r], A[i+1]
return i + 1
def select(A, p, r, i):
length = r - p + 1
if length <= 5: # 递归基本情况:返回第i小的数或返回中位数的中位数
return sorted(A[p:r+1])[i-1]
group = []
for j in range(p, r+1, 5):
if j + 5 <= r:
temp = sorted(A[j:j+5]) # 代表插入排序
else:
temp = sorted(A[j:r+1])
group.append(temp)
medians = [g[get_median_idx(len(g))] for g in group]
m_medians = select(medians, 0, len(medians)-1, get_median_idx(len(medians))+1) # 递归寻找中位数的中位数
q = partition_for_select(A, p, r, m_medians) # 中位数的中位数一定在A[p...r]
k = q - p + 1
if i == k:
return A[q]
elif i < k:
return select(A, p, q-1, i)
else:
return select(A, q+1, r, i-k)
def test():
B = list(range(1, 21))
for i in range(1, len(B)+1):
print(select(B, 0, len(B)-1, i), end=' ')
if __name__ == '__main__':
test()