type
status
date
slug
summary
tags
category
icon
password
上一个篇章我们介绍了快速排序的算法原理,以及分析了“基本快速排序”、“随机快速排序”、”三数取中快速排序“的算法实现跟各自的弊端,得知在含有大量相同元素的情况下算法性能上存在有问题。这一篇张我们介绍如何优化算法以解决这个问题。

两路快速排序

基本概念

两路快速排序(Two-Way Quick Sort)是对基本快速排序的又一种改进。在这个变体中,分区操作跟一路快速排序有所不一样,会同时从数组的两端开始,分别向中间进行扫描,直到找到需要交换的一对元素为止,然后交换这对元素的位置。

算法解释

  1. 在两路快速排序中,我们首先选择第一个元素作为基准值,然后设置两个指针i和j分别指向数组的头部尾部
  1. 两个指针分别向中间移动,右指针j寻找比基准值小的元素,左指针i寻找比基准值大的元素,当找到这样的一对元素时,交换它们的位置。注意:需要先移动右指针j
  1. 重复这个过程直到两个指针相遇,此时便找到基准值所在位置j。
  1. 交换基准值与右指针j所指元素,一次分区流程即结束。
  1. 递归左区间以及右区间,重复执行上述步骤,直到每个区间内剩下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 = 7pivot(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)的元素、等于基准值的元素以及大于基准值的元素。这种方法可以有效减少不必要的交换操作,并且在某些情况下能够提供更好的性能。

算法解释

  1. 选取基准值:与其他快速排序变体类似,需要选取一个基准值。
  1. 分区操作:
    1. 初始化三个指针,lt(小于区的右边界),gt(大于区的左边界)和i(当前考察的元素指针)。开始时,lt指向数组头部,gt指向数组末尾,i指向数组头部下一个。
    2. i位置开始,向右遍历数组,直到igt相遇。对于每个元素,进行如下操作:
        • 如果arr[i] < pivot,将arr[i]arr[lt]交换,然后lti都向右移动一位。
        • 如果arr[i] > pivot,将arr[i]arr[gt]交换,gt向左移动一位,i不动。
        • 如果arr[i] == pivoti向右移动一位。
    3. 经过上述步骤后,数组被分为三部分:索引lt左侧的所有元素都小于基准值,索引ltgt之间的所有元素都等于基准值,索引gt右侧的所有元素都大于基准值。
    4. lt左侧和gt右侧的子数组递归执行上述步骤。
  1. 递归排序:分别对小于和大于基准值的部分递归进行快速排序,不需要对等于基准值的部分进行排序,因为它们已经处于正确的位置。

代码示例

过程说明

我们仍然以代码中的数据 [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 = 7pivot(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)]

解决的问题

三路快速排序特别适用于处理包含大量重复元素的数组。在这种情况下,传统的快速排序可能会进行很多不必要的比较和交换操作,而三路快速排序能够避免这些操作,因此在一定程度上提高了排序的效率。此外,三路快速排序也减少了递归调用的深度,从而降低了栈空间的使用。
 

参考文章

 
算法解惑:快速选择算法解惑:快速排序(一)
Loading...