type
status
date
slug
summary
tags
category
icon
password
上一个篇章我们介绍了快速排序的算法原理,以及分析了“基本快速排序”、“随机快速排序”、”三数取中快速排序“的算法实现跟各自的弊端,得知在含有大量相同元素的情况下算法性能上存在有问题。这一篇张我们介绍如何优化算法以解决这个问题。
两路快速排序
基本概念
两路快速排序(Two-Way Quick Sort)是对基本快速排序的又一种改进。在这个变体中,分区操作跟一路快速排序有所不一样,会同时从数组的两端开始,分别向中间进行扫描,直到找到需要交换的一对元素为止,然后交换这对元素的位置。
算法解释
- 在两路快速排序中,我们首先选择第一个元素作为基准值,然后设置两个指针i和j分别指向数组的头部和尾部。
- 两个指针分别向中间移动,右指针j寻找比基准值小的元素,左指针i寻找比基准值大的元素,当找到这样的一对元素时,交换它们的位置。注意:需要先移动右指针j!
- 重复这个过程直到两个指针相遇,此时便找到基准值所在位置j。
- 交换基准值与右指针j所指元素,一次分区流程即结束。
- 递归左区间以及右区间,重复执行上述步骤,直到每个区间内剩下1个或0个元素,排序终止。
代码示例
过程说明
我们仍然以代码中的数据
[23,4,41,8,65,34,8,22,7,84,14,8]
为例进行说明第一次分区:
low = 0
, high = 11
, pivot = 23
,流程如下第一次分区结果:[22,4,8,8,14,7,8,23,34,84,65,41] 其中
j = 7
为 pivot(23)
的最终位置。通过对比可以看出,两路排序比一路排序的第一次分区步骤就少不少。然后对左边区域
low = 0
, high = 6
进行递归分区,依次得到结果:[(8,4,8,8,14,7,22),23,34,84,65,41]
[(7,4,8,8,8,14),22,23,34,84,65,41]
[(4,7,8,8),8,14,22,23,34,84,65,41]
[(4),7,8,8,8,14,22,23,34,84,65,41]
[4,7,(8,8),8,14,22,23,34,84,65,41]
[4,7,8,(8),8,14,22,23,34,84,65,41]
[4,7,8,8,8,(14),22,23,34,84,65,41]
对右边区域
low = 7
, high = 11
进行递归分区,依次得到结果:[4,7,8,8,8,14,22,23,(34,84,65,41)]
[4,7,8,8,8,14,22,23,34,(41,65,84)]
[4,7,8,8,8,14,22,23,34,(41,65),84]
[4,7,8,8,8,14,22,23,34,41,(65),84]
得到最终排序结果:
[4,7,8,8,8,14,22,23,34,41,65,84]
优势
两路快速排序相对于一路快速排序的优势:更好的处理重复元素,更平衡的分区。
两路快速排序通过双向扫描,能够有效地处理重复元素,使得递归树更加平衡,从而提高算法的整体性能。
三向切分快速排序
基本概念
三向切分快速排序,又称三路快速排序(Three-Way Quick Sort)是快速排序的变体之一,它在处理含有大量重复元素的数组时表现更为出色。该算法将数组分为三个部分:小于基准值(pivot)的元素、等于基准值的元素以及大于基准值的元素。这种方法可以有效减少不必要的交换操作,并且在某些情况下能够提供更好的性能。
算法解释
- 选取基准值:与其他快速排序变体类似,需要选取一个基准值。
- 分区操作:
- 初始化三个指针,
lt
(小于区的右边界),gt
(大于区的左边界)和i
(当前考察的元素指针)。开始时,lt
指向数组头部,gt
指向数组末尾,i
指向数组头部下一个。 - 从
i
位置开始,向右遍历数组,直到i
与gt
相遇。对于每个元素,进行如下操作: - 如果
arr[i] < pivot
,将arr[i]
与arr[lt]
交换,然后lt
和i
都向右移动一位。 - 如果
arr[i] > pivot
,将arr[i]
与arr[gt]
交换,gt
向左移动一位,i
不动。 - 如果
arr[i] == pivot
,i
向右移动一位。 - 经过上述步骤后,数组被分为三部分:索引
lt
左侧的所有元素都小于基准值,索引lt
到gt
之间的所有元素都等于基准值,索引gt
右侧的所有元素都大于基准值。 - 对
lt
左侧和gt
右侧的子数组递归执行上述步骤。
- 递归排序:分别对小于和大于基准值的部分递归进行快速排序,不需要对等于基准值的部分进行排序,因为它们已经处于正确的位置。
代码示例
过程说明
我们仍然以代码中的数据
[23,4,41,8,65,34,8,22,7,84,14,8]
为例进行说明第一次分区:
low = 0
, high = 11
, pivot = 23
,流程如下第一次分区结果:[22,4,8,8,14,7,8,23,34,84,65,41] 其中
j = 7
为 pivot(23)
的最终位置,同时lt = gt = 7
然后对左边区域
low = 0
, high = 6
进行递归分区,依次得到结果:[(4,8,14,7,8,22,8),23,84,34,65,41]
[4,(7,8,8,8,22,14),23,84,34,65,41]
[4,7,8,8,8,(14,22),23,84,34,65,41]
[4,7,8,8,8,(14),22,23,84,34,65,41]
对右边区域
low = 8
, high = 11
进行递归分区,依次得到结果:[4,7,8,8,8,14,22,23,(34,65,41,84)]
[4,7,8,8,8,14,22,23,(34,41,65),84]
[4,7,8,8,8,14,22,23,34,(41,65),84]
[4,7,8,8,8,14,22,23,34,41,(65),84]
得到最终排序结果:
[(4,7,8,8,8,14,22,23,34,41,65,84)]
解决的问题
三路快速排序特别适用于处理包含大量重复元素的数组。在这种情况下,传统的快速排序可能会进行很多不必要的比较和交换操作,而三路快速排序能够避免这些操作,因此在一定程度上提高了排序的效率。此外,三路快速排序也减少了递归调用的深度,从而降低了栈空间的使用。
参考文章
- 作者:VyronLee
- 链接:https://vyronlee.com/article/86821eee-5b15-4fd5-96fb-b24b1a847965
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。