快速选择
简介
快速选择,quick select,是一种从无序列表中找到第 k 小元素的选择算法,也可以拓展到找到第 k 大元素。
它的原理是快速排序,不过跟快速排序不一样的是:
快速排序:
选择 pivot 之后,
将较小的数字划分到 pivot 的左边,
较大的数字划分到右边,
再对左右两边的数字分别递归排序。
快速选择:
选择 pivot 之后,
将较小的数字划分到 pivot 的左边,
较大的数字划分到右边。
从这里开始就是根快速排序不一样的地方。
如果 pivot 刚好是第 k 个元素,直接返回,
如果左边元素多于 k 个,就对左边的元素进行递归排序,右边的元素不用管了,
反之则对右边的元素进行递归排序,左边的元素不用管,
直到找到第 k 个元素。
partition 算法
partition 算法指选择 pivot 后,将元素划分成左右两大阵营的算法,一般是原地修改数组,有以下两种方法:
Lomuto Partition
Hoare Partition
Lomuto Partition
算法
选择数组最后一个元素作为 pivot
用指针 j 遍历数组(不包括最后一个元素),同时维护一个指针 i
当
arr[j] <= pivot
的时候,交换指针 i, j 的元素,然后 i += 1最后将数组最后一个元素与
arr[i]
进行交换
代码
JavaScript Code
Hoare Partition
算法
选择数组第一个元素作为 pivot
用两个指针 l, r 一头一尾同时遍历数组
l 指针:当
arr[l] <= pivot
的时候,说明arr[l]
不需要换位置,l 指针继续右移r 指针:当
arr[r] > pivot
的时候,说明arr[r]
不需要换位置,r 指针继续左移如果 l, r 指针碰到一起了,就返回下标 r
不然,说明此时
arr[l] > pivot
并且arr[r] <= pivot
,需要将 l, r 两个位置的元素互换
代码
JavaScript Code
注意点
Hoare Partition 结束后,返回的下标是左右两大阵营的分界点,但是 pivot 不一定在这个位置。所以递归排序左边元素的时候,这个下标也要包含进去,所以递归排序左边元素的区间是
[l, p]
,而递归排序右边元素的区间则是[p+1, r]
。跟 Hoare Partition 不同,Lomuto Partition 的 pivot 元素刚好在分界点,所以递归排序的时候可以排除它。对于选择第 k 小的元素,Hoare Partition 需要选择最左边的元素作为 pivot;如果选择最右边元素作为 pivot 而 pivot 刚好是数组中最大的元素,则会陷入无限递归中。因为所有小于等于它的元素都在它左边,下一次递归排序左侧的时候实际上还是整个数组。如果 pivot 是随机选的,为了避免无限递归,可以将 pivot 和第一个元素互换位置。
Hoare Partition 比 Lomuto Partition 效率更高,但也不是稳定的排序算法。如果数组已经排序,则两种 Partition 算法都会导致快速选择的时间复杂度会降级到 $O(n^2)$。
快速选择算法
JavaScript Code
要改成寻找第 k 大的元素,修改下 partition 算法就行了。
复杂度分析
时间复杂度:$O(N)$,平均是 $O(N)$,虽然理论上最差情况是 $O(N^2)$,但实际应用的话效率还是不错的。
空间复杂度:$O(N)$,最差的情况是每次只能排除一个元素,所以递归深度应该是 $O(N)$。
Last updated