快速排序的三种划分(Partition)方法
字数 2609 2025-11-07 22:15:37
快速排序的三种划分(Partition)方法
题目描述
在快速排序算法中,划分(Partition)是核心步骤,其目标是将一个数组根据某个基准元素(pivot)重新排列,使得所有小于pivot的元素都在其左侧,所有大于pivot的元素都在其右侧。不同的划分方法会影响算法的性能和代码实现。本题要求掌握并实现三种经典的划分方法:Lomuto划分法、Hoare划分法、以及三路划分法。
解题过程
-
理解划分的目标
给定一个数组arr和其左右边界left和right,选择一个基准元素pivot。划分操作完成后,数组应满足:arr[left...p-1]中的所有元素 <=pivot(对于Lomuto和Hoare) 或< pivot(对于三路划分的严格左段)。arr[p+1...right]中的所有元素 >=pivot(对于Lomuto和Hoare) 或> pivot(对于三路划分的严格右段)。- 基准元素
pivot自身位于最终的正确位置p(对于Lomuto和Hoare) 或位于中间段 (对于三路划分)。
-
方法一:Lomuto划分法
这是最直观、最容易理解的划分方法。-
步骤详解:
- 选择基准: 通常选择最右侧的元素
arr[right]作为基准pivot。 - 初始化指针: 设置一个指针
i,初始化为left - 1。i及其左边的所有元素都代表“已处理且小于等于pivot”的部分。 - 遍历数组: 使用另一个指针
j从left遍历到right - 1(因为right是pivot)。 - 比较与交换: 对于每个
arr[j]:- 如果
arr[j] <= pivot,说明这个元素应该属于“小于等于pivot”的区域。我们将这个区域扩大一位:先执行i = i + 1,然后交换arr[i]和arr[j]。 - 如果
arr[j] > pivot,则不做任何操作,j继续向后移动。
- 如果
- 放置基准: 遍历完成后,
i指向的是“小于等于pivot”区域的最后一个元素。此时,i+1就是pivot应该在的正确位置。我们将arr[i+1]和arr[right](即pivot)交换。 - 返回基准位置: 返回
i+1作为本次划分后基准的最终位置。
- 选择基准: 通常选择最右侧的元素
-
代码示例(Python):
def lomuto_partition(arr, left, right): pivot = arr[right] # 选择最右元素为基准 i = left - 1 # 小于等于区的右边界 for j in range(left, right): # j 从 left 遍历到 right-1 if arr[j] <= pivot: i += 1 arr[i], arr[j] = arr[j], arr[i] # 将小元素交换到小于等于区 # 将基准放置到正确位置 arr[i + 1], arr[right] = arr[right], arr[i + 1] return i + 1 # 返回基准的最终位置 -
特点: 代码简洁,但当所有元素都相等时,会产生最坏情况
O(n²),因为每次划分都极度不平衡(一部分为n-1,另一部分为0)。
-
-
方法二:Hoare划分法
这是快速排序发明者Tony Hoare提出的原始方法,通常比Lomuto法更高效,交换次数更少。-
步骤详解:
- 选择基准: 通常选择最左侧的元素
arr[left]作为基准pivot。 - 初始化指针: 设置两个指针,
i初始化为left - 1,j初始化为right + 1。 - 相向扫描:
- 让
i从左向右移动,直到找到一个>= pivot的元素。 - 让
j从右向左移动,直到找到一个<= pivot的元素。 - 如果此时
i < j,则交换arr[i]和arr[j]。因为arr[i]这个“大”的在左边不对,arr[j]这个“小”的在右边也不对,交换它们就向正确迈进了一步。
- 让
- 终止条件: 重复步骤3,直到
i >= j。此时,j就是划分的边界。 - 返回边界: 返回
j。注意,基准元素不一定在j的位置,但保证arr[left...j] <= pivot且arr[j+1...right] >= pivot。
- 选择基准: 通常选择最左侧的元素
-
代码示例(Python):
def hoare_partition(arr, left, right): pivot = arr[left] # 选择最左元素为基准 i = left - 1 j = right + 1 while True: # 注意:先移动指针,再比较 i += 1 while arr[i] < pivot: # 找第一个 >= pivot 的 i += 1 j -= 1 while arr[j] > pivot: # 找第一个 <= pivot 的 j -= 1 if i >= j: return j # 返回划分边界 j arr[i], arr[j] = arr[j], arr[i] -
特点: 效率更高,但理解稍复杂。注意在递归调用时,区间应划分为
[left, j]和[j+1, right]。
-
-
方法三:三路划分法
这是处理包含大量重复元素的数组时的最优方法,可以将复杂度优化到接近O(n)。-
步骤详解:
- 选择基准: 同样选择一个基准
pivot(例如arr[left])。 - 初始化指针: 设置三个指针:
lt(less than):lt及其左边的所有元素都 严格小于 pivot。初始为left - 1。i(current):当前正在检查的元素索引。初始为left。gt(greater than):gt及其右边的所有元素都 严格大于 pivot。初始为right + 1。
- 遍历与分类: 当
i < gt时,循环进行:- 如果
arr[i] < pivot:说明它属于“小”区。将arr[i]与lt+1位置的元素交换,然后lt++和i++。因为lt+1可能是等于区的第一个元素,交换后等于区的元素被挪到了后面,而小的元素被挪到了前面。 - 如果
arr[i] == pivot:说明它就在正确的位置(中间段),直接i++即可。 - 如果
arr[i] > pivot:说明它属于“大”区。将arr[i]与gt-1位置的元素交换,然后gt--。注意,此时i不要自增,因为从gt-1换过来的元素还没有检查过。
- 如果
- 划分结果: 循环结束后,数组被划分为三段:
[left, lt]:严格小于pivot的元素。[lt+1, gt-1]:等于pivot的元素。[gt, right]:严格大于pivot的元素。
- 递归: 只需要对严格小于和严格大于的两段进行递归排序,中间等于pivot的段已经有序。
- 选择基准: 同样选择一个基准
-
代码示例(Python):
def three_way_partition(arr, left, right): if left >= right: return pivot = arr[left] lt = left - 1 i = left gt = right + 1 while i < gt: if arr[i] < pivot: lt += 1 arr[lt], arr[i] = arr[i], arr[lt] i += 1 elif arr[i] == pivot: i += 1 else: # arr[i] > pivot gt -= 1 arr[gt], arr[i] = arr[i], arr[gt] # 递归排序小于区和大于区 # quick_sort_3way(arr, left, lt) # quick_sort_3way(arr, gt, right) return lt, gt # 返回两个边界 -
特点: 有效处理重复元素,是工程实践中首选的快速排序优化方案之一。
-
总结
- Lomuto法:易于理解和实现,是教学中的首选,但效率略低。
- Hoare法:快速排序的原始方法,交换次数少,效率更高。
- 三路划分法:应对含有大量重复元素的数组时性能最佳,可以避免重复元素导致的劣化。在实际应用中(如Java的
Arrays.sort()),三路划分是标准库的常见选择。