0

0

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

DDD

DDD

发布时间:2025-09-30 09:49:24

|

240人浏览过

|

来源于php中文网

原创

高效实现快速排序: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;
}

工作原理细分:

微信 WeLM
微信 WeLM

WeLM不是一个直接的对话机器人,而是一个补全用户输入信息的生成模型。

下载
  1. 基准值选择: 示例代码选择子数组的第一个元素 arg[startIndex] 作为基准值 pivotVal。
  2. 指针初始化:
    • i 指针从 startIndex 开始(子数组的左端)。
    • j 指针从 endIndex 开始(子数组的右端,但由于 endIndex 是独占的,j 实际上指向子数组最后一个元素的下一位)。
  3. 双向遍历与交换:
    • 右侧遍历 (j 指针): while (i = pivotVal))
      • --j:j 指针首先向左移动一位,指向当前子数组的最后一个元素。
      • arg[j] >= pivotVal:如果 j 指向的元素大于或等于基准值,说明它在正确的一侧,j 继续向左移动。
      • 当 j 停止时,arg[j] 要么小于 pivotVal,要么 i 和 j 相遇。如果 arg[j]
      • arg[i] = arg[j]:将这个小于基准值的元素移动到 i 指针当前的位置(i 此时指向一个大于基准值的元素或待填充位置)。
    • 左侧遍历 (i 指针): while (i
    • ++i:i 指针首先向右移动一位。
    • arg[i]
    • 当 i 停止时,arg[i] 要么大于 pivotVal,要么 i 和 j 相遇。如果 arg[i] > pivotVal 且 i
    • 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. 分区: 调用 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开发者来说都是一项宝贵的技能。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

779

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

722

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

727

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

394

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

398

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

444

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

428

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16860

2023.08.03

excel制作动态图表教程
excel制作动态图表教程

本专题整合了excel制作动态图表相关教程,阅读专题下面的文章了解更多详细教程。

30

2025.12.29

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.1万人学习

C# 教程
C# 教程

共94课时 | 5.6万人学习

Java 教程
Java 教程

共578课时 | 39.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

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