快速排序(Quick Sort)
1 摘要
时间复杂度: 最好、平均
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn);最坏
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
l
o
g
n
)
O(logn)
O(logn)
是否稳定: 不稳定
2 算法细节
2.1 算法思想
如果要对数组区间 [p, r] 的数据进行排序,
- 先选择其中任意一个数据作为 pivot(分支点),一般为区间最后一个元素。
- 然后遍历数组,将小于 pivot 的数据放到左边,将大于 pivot 的数据放到右边。
- 接着,我们再递归对左右两边的数据进行排序。
- 直到区间缩小为 1 ,说明所有的数据都排好了序。
递推公式: quick_sort(p…r) = quick_sort(p…q-1) + quick_sort(q+1, r) 终止条件: p >= r
快速排序的具体分区过程如下所示:
- 从左到右依次遍历数组,定义 i 和 j 指针,其中 i 指向大于pivot的元素的起始位置,j 用于遍历所有元素,以便将其与pivot进行比较;
- 初始化时,均指向数组起始位置;
- 如遇到小于 pivot 的元素,则进行数据交换,i 与 j 所指的数据进行交换,即将小于pivot的元素放于索引 i ,紧接着 i 会加一;否则继续往前进行(即不做任何交换,两个指针各加1);
- 最后再将 pivot 放于大于 pivot 和小于 pivot 的分界位置,即 p 与 i 所指数据进行交换。
2.2 算法实现
def quickSort(nums, start, end):
# 由于本算法是直接对数组进行改变,所以针对于非法输入的数组可以直接忽略
if start < end:
pivot = nums[end]
i = j = start
while j < end:
if nums[j] < pivot:
nums[i], nums[j] = nums[j], nums[i]
i += 1
j += 1
# 此时j = end,所以下式等价于交换nums[j]和nums[i]
nums[i], nums[end] = nums[end], nums[i]
quickSort(nums[:i], start, i-1)
quickSort(nums[i+1:end], i+1, end)
# 直接对数组进行排序操作,改变索引,没有返回值
- 递归调用时,注意终止条件,当nums的长度小于2时,直接返回;
- 当长度大于等于2时,进行排序,并修改nums中元素的位置。
2.3 与归并排序的对比
- 归并排序是由下向上的,先处理子数组然后再合并。
- 归并排序非常稳定,时间复杂度始终都是 O ( n l o g n ) O(nlogn) O(nlogn),但它是非原地算法。
- 而快速排序正好相反,它的过程是由上向下的,先分出两个子区间,再对子区间进行排序。
- 最坏情况:如果数据原来已经是有序的,则每次的分区都是不均等的,我们需要进行 n 次分区才能完成整个排序,此时快排的时间复杂度就退化成了 O ( n 2 ) O(n^2) O(n2),但平均情况下时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),最坏情况发生的概率也比较小;
- 是原地排序算法,因此应用得更加广泛。
- 是一个不稳定的排序算法,因为其在数据交换过程中可能会改变相等元素的原始位置。
参考: