首页 > Java > java教程 > 正文

高效实现快速排序:Hoare分区法详解

DDD
发布: 2025-09-30 09:49:24
原创
210人浏览过

高效实现快速排序:hoare分区法详解

本文深入探讨了一种基于Hoare分区策略的快速排序算法实现。通过详细分析其双指针遍历与元素交换机制,以及递归调用处理子数组的逻辑,文章旨在提供一个清晰、专业的教程,帮助读者理解并掌握这种高效的排序方法。

快速排序概述

快速排序(QuickSort)是一种广泛使用的、基于比较的排序算法,以其平均时间复杂度为O(n log n)而闻名。其核心思想是“分而治之”:选择一个“基准元素”(pivot),通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比基准元素小,另一部分记录的关键字均比基准元素大,然后分别对这两部分记录继续进行排序,直到整个序列有序。

快速排序的性能很大程度上取决于基准元素的选择和分区(Partition)策略的实现。常见的有Lomuto分区方案和Hoare分区方案。本文将重点介绍一种Hoare分区法的实现,它通过双指针从两端向中间移动来完成分区操作。

Hoare分区法详解

Hoare分区法通常被认为比Lomuto分区法更高效,尤其是在处理重复元素较多的数组时。它的主要思想是,选择数组的第一个元素作为基准值,然后使用两个指针(i 和 j)分别从数组的两端向中间移动,寻找需要交换的元素。

1. getPivotIndex 方法:实现分区逻辑

getPivotIndex 方法是整个快速排序算法的核心,它负责将当前子数组根据基准值划分为两部分,并返回基准值最终的索引。

private static int getPivotIndex(int[] arg, int startIndex, int endIndex) {
    int pivotVal = arg[startIndex]; // 选择子数组的第一个元素作为基准值
    int i = startIndex;
    int j = endIndex; // 注意:endIndex在此处是独占的,即子数组范围是 [startIndex, endIndex-1]

    while (i < j) {
        // 从右向左遍历,寻找第一个小于基准值的元素
        // 遇到大于等于基准值的元素时,j指针继续向左移动
        while (i < j && (arg[--j] >= pivotVal));
        // 如果找到小于基准值的元素(且i < j),将其移动到i指针当前位置
        if (i < j)
            arg[i] = arg[j];

        // 从左向右遍历,寻找第一个大于基准值的元素
        // 遇到小于等于基准值的元素时,i指针继续向右移动
        while (i < j && (arg[++i] <= pivotVal));
        // 如果找到大于基准值的元素(且i < j),将其移动到j指针当前位置
        if (i < j)
            arg[j] = arg[i];

    } // 当i >= j时,循环结束,此时i和j指向同一个位置,即基准值的最终位置

    // 将基准值放到其最终的排序位置
    arg[j] = pivotVal;
    // 返回基准值的索引
    return j;
}
登录后复制

工作原理细分:

简篇AI排版
简篇AI排版

AI排版工具,上传图文素材,秒出专业效果!

简篇AI排版 554
查看详情 简篇AI排版
  1. 基准值选择: 示例代码选择子数组的第一个元素 arg[startIndex] 作为基准值 pivotVal。
  2. 指针初始化:
    • i 指针从 startIndex 开始(子数组的左端)。
    • j 指针从 endIndex 开始(子数组的右端,但由于 endIndex 是独占的,j 实际上指向子数组最后一个元素的下一位)。
  3. 双向遍历与交换:
    • 右侧遍历 (j 指针): while (i < j && (arg[--j] >= pivotVal))
      • --j:j 指针首先向左移动一位,指向当前子数组的最后一个元素。
      • arg[j] >= pivotVal:如果 j 指向的元素大于或等于基准值,说明它在正确的一侧,j 继续向左移动。
      • 当 j 停止时,arg[j] 要么小于 pivotVal,要么 i 和 j 相遇。如果 arg[j] < pivotVal 且 i < j,则说明找到了一个应该放在左侧但目前在右侧的元素。
      • arg[i] = arg[j]:将这个小于基准值的元素移动到 i 指针当前的位置(i 此时指向一个大于基准值的元素或待填充位置)。
    • 左侧遍历 (i 指针): while (i < j && (arg[++i] <= pivotVal))
      • ++i:i 指针首先向右移动一位。
      • arg[i] <= pivotVal:如果 i 指向的元素小于或等于基准值,说明它在正确的一侧,i 继续向右移动。
      • 当 i 停止时,arg[i] 要么大于 pivotVal,要么 i 和 j 相遇。如果 arg[i] > pivotVal 且 i < j,则说明找到了一个应该放在右侧但目前在左侧的元素。
      • arg[j] = arg[i]:将这个大于基准值的元素移动到 j 指针当前的位置(j 此时指向一个小于基准值的元素或待填充位置)。
  4. 基准值归位: 当 i 和 j 指针相遇或交叉时,循环结束。此时 j 指针(或 i 指针,因为它们指向同一个位置)所指向的位置就是 pivotVal 最终的正确位置。将最初的 pivotVal 放入 arg[j]。
  5. 返回基准值索引: 返回 j 作为基准值的新索引。

2. quickSort 方法:递归排序子数组

quickSort 方法负责处理整个排序过程,它通过递归调用自身来对分区后的子数组进行排序。

static void quickSort(int[] arg, int startIndex, int endIndex) {
    // 基本情况:如果子数组的长度小于2,则无需分区,直接返回
    // 数组长度为0或1时,认为已经有序
    if (endIndex - startIndex < 2)
        return;

    // 调用getPivotIndex方法进行分区,获取基准值的最终索引
    int pivotIndex = getPivotIndex(arg, startIndex, endIndex);

    // 递归排序基准值左侧的子数组
    quickSort(arg, startIndex, pivotIndex);
    // 递归排序基准值右侧的子数组
    quickSort(arg, pivotIndex + 1, endIndex);
}
登录后复制

工作原理:

  1. 基本情况: if (endIndex - startIndex < 2) 是递归的终止条件。当子数组只包含一个元素或没有元素时,它自然就是有序的,不需要再进行排序。
  2. 分区: 调用 getPivotIndex 方法,将当前子数组 arg[startIndex...endIndex-1] 分区。pivotIndex 是基准值在排序后的最终位置。
  3. 递归调用:
    • quickSort(arg, startIndex, pivotIndex):对基准值左侧的子数组进行递归排序。注意,这里的 pivotIndex 是独占的,所以左侧子数组的范围是 [startIndex, pivotIndex-1]。
    • quickSort(arg, pivotIndex + 1, endIndex):对基准值右侧的子数组进行递归排序。右侧子数组的范围是 [pivotIndex + 1, endIndex-1]。

完整代码示例

public class QuickSort_Impl {

    public static void main(String[] args) {
        int[] unsortedArray = {12, 3, 45, 23, 6, -4, -6, 10, 1, 8};

        // 调用快速排序方法
        // 注意:endIndex通常是数组长度,表示排序范围是 [0, unsortedArray.length-1]
        quickSort(unsortedArray, 0, unsortedArray.length);

        // 打印排序后的数组
        System.out.println("排序后的数组:");
        for (int i : unsortedArray)
            System.out.print(i + " ");
        System.out.println();
    }

    /**
     * 快速排序主方法
     *
     * @param arg        待排序数组
     * @param startIndex 子数组起始索引(包含)
     * @param endIndex   子数组结束索引(不包含)
     */
    static void quickSort(int[] arg, int startIndex, int endIndex) {
        // 基本情况:如果子数组的长度小于2,则无需分区,直接返回
        if (endIndex - startIndex < 2)
            return;

        // 调用getPivotIndex方法进行分区,获取基准值的最终索引
        int pivotIndex = getPivotIndex(arg, startIndex, endIndex);

        // 递归排序基准值左侧的子数组
        quickSort(arg, startIndex, pivotIndex);
        // 递归排序基准值右侧的子数组
        quickSort(arg, pivotIndex + 1, endIndex);
    }

    /**
     * Hoare分区法实现,将数组根据基准值分为两部分,并返回基准值的最终索引
     *
     * @param arg        待分区数组
     * @param startIndex 子数组起始索引(包含)
     * @param endIndex   子数组结束索引(不包含)
     * @return 基准值的最终索引
     */
    private static int getPivotIndex(int[] arg, int startIndex, int endIndex) {
        int pivotVal = arg[startIndex]; // 选择子数组的第一个元素作为基准值
        int i = startIndex;
        int j = endIndex; // 注意:endIndex在此处是独占的,即子数组范围是 [startIndex, endIndex-1]

        // 当i < j时,双指针继续移动和交换
        while (i < j) {
            // 从右向左遍历,寻找第一个小于基准值的元素
            // j指针先自减,再进行比较
            while (i < j && (arg[--j] >= pivotVal));
            // 如果找到小于基准值的元素,将其移动到i指针当前位置
            if (i < j)
                arg[i] = arg[j];

            // 从左向右遍历,寻找第一个大于基准值的元素
            // i指针先自增,再进行比较
            while (i < j && (arg[++i] <= pivotVal));
            // 如果找到大于基准值的元素,将其移动到j指针当前位置
            if (i < j)
                arg[j] = arg[i];

        } // 循环结束时,i和j指向同一个位置,即基准值的最终位置

        // 将基准值放到其最终的排序位置
        arg[j] = pivotVal;
        // 返回基准值的索引
        return j;
    }
}
登录后复制

注意事项与总结

  1. 基准值选择: 本示例选择子数组的第一个元素作为基准值。虽然简单,但在某些极端情况下(如已排序或逆序数组)可能导致性能下降到O(n^2)。更优的策略包括随机选择基准值或“三数取中”法。
  2. endIndex 的含义: 在本实现中,endIndex 是一个独占索引,表示子数组的范围是 [startIndex, endIndex-1]。理解这一点对于正确编写递归边界和指针移动逻辑至关重要。
  3. 性能: Hoare分区法在平均情况下表现优秀,时间复杂度为O(n log n)。虽然在最坏情况下(如基准值选择不当)会退化到O(n^2),但通过优化基准值选择可以有效避免。
  4. 空间复杂度: 快速排序的空间复杂度主要取决于递归的深度,平均情况下为O(log n),最坏情况下为O(n)。
  5. 稳定性: 快速排序通常不是一个稳定排序算法,即相等元素的相对顺序在排序后可能会改变。

通过本文的详细解析和代码示例,读者应该能够清晰地理解并实现基于Hoare分区法的快速排序算法。掌握这种高效的排序技术对于任何Java开发者来说都是一项宝贵的技能。

以上就是高效实现快速排序:Hoare分区法详解的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号