This commit is contained in:
krahets
2023-08-20 23:27:50 +08:00
parent 68e11cfa34
commit 4d55cafd13
46 changed files with 174 additions and 162 deletions

View File

@@ -4,9 +4,9 @@ comments: true
# 11.5   快速排序
「快速排序 Quick Sort」是一种基于分治思想的排序算法运行高效应用广泛。
「快速排序 quick sort」是一种基于分治思想的排序算法运行高效应用广泛。
快速排序的核心操作是哨兵划分,其目标是:选择数组中的某个元素作为“基准数”,将所有小于基准数的元素移到其左侧,而大于基准数的元素移到其右侧。具体来说,哨兵划分的流程为:
快速排序的核心操作是哨兵划分,其目标是:选择数组中的某个元素作为“基准数”,将所有小于基准数的元素移到其左侧,而大于基准数的元素移到其右侧。具体来说,哨兵划分的流程为:
1. 选取数组最左端元素作为基准数,初始化两个指针 `i``j` 分别指向数组的两端。
2. 设置一个循环,在每轮中使用 `i``j`)分别寻找第一个比基准数大(小)的元素,然后交换这两个元素。
@@ -362,8 +362,8 @@ comments: true
## 11.5.1   算法流程
1. 首先,对原数组执行一次哨兵划分,得到未排序的左子数组和右子数组。
2. 然后,对左子数组和右子数组分别递归执行哨兵划分
1. 首先,对原数组执行一次哨兵划分,得到未排序的左子数组和右子数组。
2. 然后,对左子数组和右子数组分别递归执行哨兵划分
3. 持续递归,直至子数组长度为 1 时终止,从而完成整个数组的排序。
![快速排序流程](quick_sort.assets/quick_sort_overview.png)
@@ -594,15 +594,15 @@ comments: true
## 11.5.3   快排为什么快?
从名称上就能看出,快速排序在效率方面应该具有一定的优势。尽管快速排序的平均时间复杂度与归并排序」和「堆排序相同,但通常快速排序的效率更高,原因如下:
从名称上就能看出,快速排序在效率方面应该具有一定的优势。尽管快速排序的平均时间复杂度与归并排序”和“堆排序相同,但通常快速排序的效率更高,原因如下:
- **出现最差情况的概率很低**:虽然快速排序的最差时间复杂度为 $O(n^2)$ ,没有归并排序稳定,但在绝大多数情况下,快速排序能在 $O(n \log n)$ 的时间复杂度下运行。
- **缓存使用效率高**:在执行哨兵划分操作时,系统可将整个子数组加载到缓存,因此访问元素的效率较高。而像堆排序这类算法需要跳跃式访问元素,从而缺乏这一特性。
- **复杂度的常数系数低**:在上述三种算法中,快速排序的比较、赋值、交换等操作的总数量最少。这与插入排序」比「冒泡排序更快的原因类似。
- **缓存使用效率高**:在执行哨兵划分操作时,系统可将整个子数组加载到缓存,因此访问元素的效率较高。而像堆排序这类算法需要跳跃式访问元素,从而缺乏这一特性。
- **复杂度的常数系数低**:在上述三种算法中,快速排序的比较、赋值、交换等操作的总数量最少。这与插入排序”比“冒泡排序更快的原因类似。
## 11.5.4   基准数优化
**快速排序在某些输入下的时间效率可能降低**。举一个极端例子,假设输入数组是完全倒序的,由于我们选择最左端元素作为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,导致左子数组长度为 $n - 1$ 、右子数组长度为 $0$ 。如此递归下去,每轮哨兵划分后的右子数组长度都为 $0$ ,分治策略失效,快速排序退化为冒泡排序
**快速排序在某些输入下的时间效率可能降低**。举一个极端例子,假设输入数组是完全倒序的,由于我们选择最左端元素作为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,导致左子数组长度为 $n - 1$ 、右子数组长度为 $0$ 。如此递归下去,每轮哨兵划分后的右子数组长度都为 $0$ ,分治策略失效,快速排序退化为冒泡排序
为了尽量避免这种情况发生,**我们可以优化哨兵划分中的基准数的选取策略**。例如,我们可以随机选取一个元素作为基准数。然而,如果运气不佳,每次都选到不理想的基准数,效率仍然不尽如人意。