部分内容转载:https://blog.csdn.net/dacixie/article/details/79477421
感谢博主的整理
快速选择:
快速选择(英语:Quickselect)是一种从无序列表找到第k小元素的选择算法。它从原理上来说与快速排序有关。与快速排序一样都由托尼·霍尔提出的,因而也被称为霍尔选择算法。同样地,它在实际应用是一种高效的算法,具有很好的平均时间复杂度,然而最坏时间复杂度则不理想。快速选择及其变种是实际应用中最常使用的高效选择算法。
快速选择的总体思路与快速排序一致,选择一个元素作为基准来对元素进行分区,将小于和大于基准的元素分在基准左边和右边的两个区域。不同的是,快速选择并不递归访问双边,而是只递归进入一边的元素中继续寻找。这降低了平均时间复杂度,从O(n log n)至O(n),不过最坏情况仍然是O(n2)。
与快速排序一样,快速选择一般是以原地算法的方式实现,除了选出第k小的元素,数据也得到了部分地排序。
如果不理解快速选择的过程,因为快速选择和快速排序的过程可以说是很相近,所以建议大家先看一个短短10分钟由北京大学陈斌老师的快速排序算法讲解视频:
快速选择的总体思路与快速排序一致,选择一个元素作为基准来对元素进行分区,将小于和大于基准的元素分在基准左边和右边的两个区域。不同的是,快速选择并不递归访问双边,而是只递归进入一边的元素中继续寻找。这降低了平均时间复杂度,从O(n log n)至O(n),不过最坏情况仍然是O(n2)。
与快速排序一样,快速选择一般是以原地算法的方式实现,除了选出第k小的元素,数据也得到了部分地排序。
Quick Select的目标是找出第k大元素,所以
若切分后的左子数组的长度 > k,则第k大元素必出现在左子数组中;
若切分后的左子数组的长度 = k-1,则第k大元素为pivot;
若上述两个条件均不满足,则第k大元素必出现在右子数组中。
代码函数如下:
def qselect(A,k):
if len(A)<=k:return A #若数组长度小于k,则输出前k个最小数构成的数组
pivot = A[0] #中位数
left = [pivot] + [x for x in A[1:] if x<=pivot] #中位数左边的数组
llen = len(left) #中位数左边数组的长度,全是比中位数小的数
if llen==k: #如果刚好为k个,则第k个是第k个最小数
return left #输出数组
if llen > k: #如果比k大,则需要对这个数组进一步切分
return qselect(left, k) #递归继续划分
else:
right = [x for x in A[1:] if x > pivot] #如果长度小于k,那肯定在中位数右边的数组,也就是比中位数大
return left + [pivot] + qselect(right, k-llen-1) #返回左边+中位数+右边划分
#所以划分一共有三种情况,恰好k个,比k个少,比k个多,分别对应左、中、右;
# 划分函数需要两个参数:k/k-llen,以及被划分的数组。
刚好leetcode有一道题:
设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。
示例:
输入: arr = [1,3,5,7,2,4,6,8], k = 4
输出: [1,2,3,4]
class Solution:
def smallestK(self, arr: List[int], k: int) -> List[int]:
def helper(arr, k):
if k == 0:#个人感觉很有必要
return []
if len(arr) <= k:
return arr
# 快速选择
midnum = arr[0] # 中位数取数组第一位
left = [e for e in arr[1:] if e <= midnum]
len_left = len(left) # 左数组的长度
if len_left == k:
return left
elif len_left > k:
return helper(left, k) # 继续分
else: # 长度比k还小,只能把中位数右边的也加进来了
right = [e for e in arr[1:] if e > midnum]
return left + [midnum] + helper(right, k - len_left-1) # left已经被算进里面了,所以只需要在right里面找k-lenleft-1就好
return helper(arr,k)
如果要找出最大的K个数呢?魔改一下代码
def helper(arr, k):
if k == 0:#个人感觉很有必要
return []
if len(arr) <= k:
return arr
# 快速选择
midnum = arr[0] # 中位数取数组第一位
right = [e for e in arr[1:] if e >= midnum]
len_right = len(right) # 右数组的长度
if len_right == k:
return right
elif len_right > k:
return helper(right, k) # 继续分
else: # 长度比k还小,只能把中位数左边的也加进来了
left = [e for e in arr[1:] if e < midnum]
return right + [midnum] + helper(left, k-len_right-1) # left已经被算进里面了,所以只需要在right里面找k-lenleft-1就好