您可以在阵列上调用quickselect 两次以就地执行此操作,平均情况下是线性时间。通过使用线性时间median of medians algorithm 为快速选择选择最佳枢轴,也可以将最坏情况的运行时间提高到 O(n)。
对于 quickselect 的两次调用,使用 k = n / 3。在第一次调用时,对整个数组使用 quickselect,在第二次调用时,使用 arr[k..n-1](对于 0 -索引数组)。
快速选择的维基百科解释:
快速选择使用与快速排序相同的整体方法,选择一个
元素作为枢轴并根据
枢轴,因此小于或大于枢轴。然而,
而不是递归到双方,如快速排序,快速选择
只递归到一侧——它所在的元素的一侧
寻找。这降低了 O(n log n) 的平均复杂度(在
快速排序)到 O(n)(在快速选择中)。
与快速排序一样,快速选择通常作为就地实现
算法,除了选择第 k 个元素之外,它还部分地
对数据进行排序。请参阅selection algorithm 以进一步讨论
与排序有关。
要将数组的元素分成3组,请结合使用以下用Python编写的算法并结合快速选择:
k = n / 3
# First group smallest elements in array
quickselect(L, 0, n - 1, k) # Call quickselect on your entire array
# Then group middle elements in array
quickselect(L, k, n - 1, k) # Call quickselect on subarray
# Largest elements in array are already grouped so
# there is no need to call quickselect again
这一切的关键在于快速选择使用了一个称为分区的子程序。分区将数组分成两部分,大于给定元素的部分和小于给定元素的部分。因此,它围绕该元素对数组进行部分排序,并返回其新的排序位置。因此,通过使用快速选择,您实际上可以对第 k 个元素周围的数组进行部分排序(请注意,这与实际对整个数组进行排序不同)就地和平均情况下的线性时间。
快速选择的时间复杂度:
- 最坏情况下的性能 O(n2)
- 最佳案例性能 O(n)
- 平均案例性能 O(n)
快速选择的运行时间几乎总是线性的,而不是二次的,但这取决于这样一个事实,即对于大多数数组,简单地选择一个随机枢轴点几乎总是会产生线性运行时间。但是,如果您想提高快速选择的最坏情况性能,您可以选择在每次调用之前使用median of medians algorithm 来近似用于快速选择的最佳枢轴。这样做,您会将快速选择算法的最坏情况性能提高到 O(n)。这种开销可能不是必需的,但如果您正在处理大量随机整数列表,它可以防止您的算法出现一些异常的二次运行时。
这是一个完整的 Python 示例,它实现了快速选择并将其两次应用于 120 个整数的反向排序列表并打印出三个结果子列表。
from random import randint
def partition(L, left, right, pivotIndex):
'''partition L so it's ordered around L[pivotIndex]
also return its new sorted position in array'''
pivotValue = L[pivotIndex]
L[pivotIndex], L[right] = L[right], L[pivotIndex]
storeIndex = left
for i in xrange(left, right):
if L[i] < pivotValue:
L[storeIndex], L[i] = L[i], L[storeIndex]
storeIndex = storeIndex + 1
L[right], L[storeIndex] = L[storeIndex], L[right]
return storeIndex
def quickselect(L, left, right, k):
'''retrieve kth smallest element of L[left..right] inclusive
additionally partition L so that it's ordered around kth
smallest element'''
if left == right:
return L[left]
# Randomly choose pivot and partition around it
pivotIndex = randint(left, right)
pivotNewIndex = partition(L, left, right, pivotIndex)
pivotDist = pivotNewIndex - left + 1
if pivotDist == k:
return L[pivotNewIndex]
elif k < pivotDist:
return quickselect(L, left, pivotNewIndex - 1, k)
else:
return quickselect(L, pivotNewIndex + 1, right, k - pivotDist)
def main():
# Setup array of 120 elements [120..1]
n = 120
L = range(n, 0, -1)
k = n / 3
# First group smallest elements in array
quickselect(L, 0, n - 1, k) # Call quickselect on your entire array
# Then group middle elements in array
quickselect(L, k, n - 1, k) # Call quickselect on subarray
# Largest elements in array are already grouped so
# there is no need to call quickselect again
print L[:k], '\n'
print L[k:k*2], '\n'
print L[k*2:]
if __name__ == '__main__':
main()