快速排序实现原理
在不同的实现方法中,按照交换值的方法可以分为 赋值
和 两数值交换
,这个现在理解不了没关系,先带着这个观点继续往后看
普通快排
以升序为例,实现步骤如下图所示
function quick_sort_v1(arr: number[], l: number, r: number) {
if (l >= r) return
let x = l, y = r, base = arr[l] // 选择操作区间第一位作为基准值
while(x < y) {
while(x < y && arr[y] > base) y--
if (x < y) arr[x++] = arr[y]
while(x < y && arr[x] <= base) x++
if (x < y) arr[y--] = arr[x]
}
arr[x] = base
// 此时数组已被基准值分为大小两部分,接着对其进行递归操作
quick_sort_v1(arr, l, x - 1)
quick_sort_v1(arr, x + 1, r)
}
单边递归快排
从上面的实现方法可以看到,每次操作需要进行两次递归操作,通过单边递归我们可以将递归操作减少至1次,从而减少空间的使用
function quick_sort_v2(arr: number[], l: number, r: number) {
let x: number, y: number, base: number
while(l < r) {
x = l
y = r
base = arr[l]
while(x < y) {
while(x < y && arr[y] > base) y--
if (x < y) arr[x++] = arr[y]
while(x < y && arr[x] <= base) x++
if (x < y) arr[y--] = arr[x]
}
arr[x] = base
quick_sort_v2(arr, x + 1, r)
r = x - 1
}
}
从以上代码可以看出,原来需要的两次递归操作现在只要一次就可以了
总结一下,以上两种方法,在交换值时是直接赋值的,这意味着在程序运行时,数组是缺了一个元素的,这就意味着阈值(或者说基准值)不能随便选,只能取操作区间的头部或者尾部
以上两段代码的基准值选择都是选择了操作空间的第一个
如果随机选阈值,最后极可能会出现“吞数字”的情况,如下图所示
分区快排
分区快排结合了快排和插入排序的优点
从上面介绍的两种快排方法来看,最终运行的效率取决于中间值选得好不好(看脸)
快 排 时 间 复 杂 度 = { O ( n l o g n ) 一般 O ( l o g n ) 理想 O ( n 2 ) 最差 快排时间复杂度=\begin{cases}O(nlogn) & \text{一般} \\ O(logn) & \text{理想}\\ O(n^2) & \text{最差} \end{cases} 快排时间复杂度=⎩⎪⎨⎪⎧O(nlogn)O(logn)O(n2)一般理想最差
而插入排序的时间复杂度如下
插 入 排 序 时 间 复 杂 度 = { O ( n 2 ) 一般 O ( n ) 相对有序 插入排序时间复杂度=\begin{cases} O(n^2) & \text{一般} \\ O(n) & \text{相对有序} \end{cases} 插入排序时间复杂度={
O(n2)O(n)一般相对有序