首页 > Java > java教程 > 正文

实现 Hoare 分区方案的快速排序算法详解

碧海醫心
发布: 2025-09-30 13:56:31
原创
979人浏览过

实现 Hoare 分区方案的快速排序算法详解

本文深入探讨了基于 Hoare 分区方案的快速排序算法的 Java 实现。我们将详细解析快速排序的核心思想——分治策略,并重点讲解 Hoare 分区过程,包括枢轴选择、双指针移动及元素交换逻辑。通过完整的代码示例和逐步解释,帮助读者理解并掌握这种高效的排序算法,同时提供性能考量和实践建议,确保算法的正确性和效率。

快速排序概述

快速排序(quicksort)是一种高效的、基于比较的排序算法,其核心思想是“分而治之”(divide and conquer)。它通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据要小,然后按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,最终使整个数据序列有序。

快速排序的关键在于“分区”操作。常见的分区方案有两种:Lomuto 分区和 Hoare 分区。本教程将重点介绍 Hoare 分区方案的实现。

Hoare 分区方案详解

Hoare 分区方案是快速排序的原始设计,它通常比 Lomuto 分区更高效,因为它执行的交换次数更少。Hoare 分区的基本思想是:

  1. 选择枢轴(Pivot):从待排序的数组中选择一个元素作为枢轴。在本实现中,我们选择子数组的第一个元素作为枢轴。
  2. 双指针移动:使用两个指针 i 和 j。i 从子数组的起始位置开始向右移动,j 从子数组的结束位置开始向左移动。
  3. 比较与交换
    • 指针 j 从右向左移动,直到找到一个小于或等于枢轴的元素。
    • 指针 i 从左向右移动,直到找到一个大于或等于枢轴的元素。
    • 如果 i < j,则交换 arr[i] 和 arr[j]。
  4. 枢轴归位:当 i >= j 时,循环结束。此时,指针 j 所在的元素就是枢轴最终的正确位置。所有 j 左边的元素都小于或等于枢轴,所有 j 右边的元素都大于或等于枢轴。

这种分区方式将数组分为两部分,枢轴本身可能不在最终的 j 位置,但 j 位置是枢轴值最终应该放置的位置,且 j 将数组有效分割。

Java 实现

我们将通过一个完整的 Java 代码示例来演示如何实现基于 Hoare 分区方案的快速排序。

1. quickSort 方法

这是快速排序的递归主函数。它接收一个整数数组、起始索引和结束索引(不包含)。

快标书AI
快标书AI

10分钟生成投标方案

快标书AI 241
查看详情 快标书AI
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);
}
登录后复制

2. getPivotIndex 方法(分区逻辑)

这个方法实现了 Hoare 分区方案的核心逻辑。

private static int getPivotIndex(int[] arg, int startIndex, int endIndex) {
    // 选择子数组的第一个元素作为枢轴值
    int pivotVal = arg[startIndex];
    // 初始化左指针和右指针
    int i = startIndex;
    int j = endIndex;

    // 当左指针小于右指针时,继续进行分区
    while (i < j) {
        // 右指针从右向左移动,直到找到一个小于或等于枢轴的元素
        // 注意:这里使用 --j 是因为 endIndex 是不包含的,所以 j 初始值是 endIndex,
        // 第一次使用时会变为 endIndex - 1
        while (i < j && (arg[--j] >= pivotVal));
        // 如果左指针仍小于右指针,说明找到了一个需要移动到左侧的元素
        if (i < j) {
            arg[i] = arg[j]; // 将右侧找到的小于枢轴的元素移动到左指针当前位置
        }

        // 左指针从左向右移动,直到找到一个大于或等于枢轴的元素
        // 注意:这里使用 ++i
        while (i < j && (arg[++i] <= pivotVal));
        // 如果左指针仍小于右指针,说明找到了一个需要移动到右侧的元素
        if (i < j) {
            arg[j] = arg[i]; // 将左侧找到的大于枢轴的元素移动到右指针当前位置
        }
    } // 循环结束时,i 和 j 相遇,此时 j 的位置就是枢轴值应该放置的位置

    // 将枢轴值放到其最终的正确位置
    arg[j] = pivotVal;
    // 返回枢轴的最终索引
    return j;
}
登录后复制

3. 完整代码示例

public class QuickSortHoare {

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

        System.out.println("原始数组:");
        for (int i : unsortedArray) {
            System.out.print(i + " ");
        }
        System.out.println();

        // 调用快速排序算法
        quickSort(unsortedArray, 0, unsortedArray.length);

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

    /**
     * 快速排序主函数,采用 Hoare 分区方案。
     *
     * @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;

        // 当左指针小于右指针时,继续进行分区
        while (i < j) {
            // 右指针从右向左移动,直到找到一个小于或等于枢轴的元素
            // 注意:这里使用 --j 是因为 endIndex 是不包含的,所以 j 初始值是 endIndex,
            // 第一次使用时会变为 endIndex - 1
            while (i < j && (arg[--j] >= pivotVal));
            // 如果左指针仍小于右指针,说明找到了一个需要移动到左侧的元素
            if (i < j) {
                arg[i] = arg[j]; // 将右侧找到的小于枢轴的元素移动到左指针当前位置
            }

            // 左指针从左向右移动,直到找到一个大于或等于枢轴的元素
            // 注意:这里使用 ++i
            while (i < j && (arg[++i] <= pivotVal));
            // 如果左指针仍小于右指针,说明找到了一个需要移动到右侧的元素
            if (i < j) {
                arg[j] = arg[i]; // 将左侧找到的大于枢轴的元素移动到右指针当前位置
            }
        } // 循环结束时,i 和 j 相遇,此时 j 的位置就是枢轴值应该放置的位置

        // 将枢轴值放到其最终的正确位置
        arg[j] = pivotVal;
        // 返回枢轴的最终索引
        return j;
    }
}
登录后复制

运行与测试

要运行上述代码,您可以将其保存为 QuickSortHoare.java 文件,然后使用 Java 编译器编译并运行:

javac QuickSortHoare.java
java QuickSortHoare
登录后复制

预期输出:

原始数组:
12 3 45 23 6 -4 -6 10 1 8 
排序后的数组:
-6 -4 1 3 6 8 10 12 23 45 
登录后复制

注意事项与性能分析

  1. 枢轴选择:本实现选择子数组的第一个元素作为枢轴。虽然简单,但如果输入数组已经部分有序或完全有序,这种选择可能导致最坏情况,即每次分区都将数组分为一个空子数组和一个 n-1 大小的子数组,使得时间复杂度退化到 O(n²)。更优的枢轴选择策略包括:
    • 随机选择枢轴:从子数组中随机选择一个元素作为枢轴。
    • 三数取中法:选择子数组的第一个、中间和最后一个元素,取它们的中位数作为枢轴。
  2. 时间复杂度
    • 平均情况:O(n log n)。在大多数情况下,快速排序表现优秀。
    • 最坏情况:O(n²)。当枢轴选择不当,导致每次分区都产生极度不平衡的子数组时发生(例如,选择最大或最小元素作为枢轴)。
  3. 空间复杂度:O(log n)(平均情况),主要由递归调用的深度决定。最坏情况下,递归深度可能达到 O(n)。
  4. 稳定性:快速排序是一种不稳定的排序算法,因为它在分区过程中可能会交换相等元素的相对顺序。
  5. Hoare 分区与 Lomuto 分区
    • Hoare 分区:通常执行更少的交换操作,因此在实际应用中可能更快。它将数组分为两个子数组,枢轴最终的位置在 j 处,但 arg[j] 不一定是原始的枢轴值。
    • Lomuto 分区:保证枢轴最终会放置在其排序后的最终位置,并且其左侧所有元素都小于它,右侧所有元素都大于它。Lomuto 分区通常需要更多的交换操作。

总结

本教程详细介绍了使用 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号