0

0

深入理解快速排序:一种高效的就地分区实现

心靈之曲

心靈之曲

发布时间:2025-09-30 12:19:00

|

272人浏览过

|

来源于php中文网

原创

深入理解快速排序:一种高效的就地分区实现

本文详细阐述了快速排序算法的一种常见实现,重点介绍其核心的“就地分区”策略。通过选择一个基准值,并利用双指针技术将数组划分为小于、等于和大于基准值的区域,最终将基准值放置到其最终排序位置。随后,算法递归地对左右子数组进行排序,从而实现高效的整体排序。

引言:快速排序概述

快速排序(quicksort)是一种高效的、基于比较的排序算法,由c.a.r. hoare在1960年提出。它采用了“分而治之”(divide and conquer)的思想,其核心在于通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据要小,然后按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

快速排序的主要步骤包括:

  1. 选择基准值(Pivot Selection):从数组中选择一个元素作为基准值。
  2. 分区(Partitioning):重新排列数组,将所有小于基准值的元素移到基准值的左边,所有大于基准值的元素移到基准值的右边。在这个过程中,基准值会移动到其最终的排序位置。
  3. 递归排序(Recursive Sort):递归地对基准值左右两边的子数组进行快速排序。

核心机制:就地分区(In-Place Partitioning)

分区操作是快速排序算法的灵魂。其目标是选择一个基准值,并通过一系列交换操作,将数组分为两部分:一部分包含所有小于基准值的元素,另一部分包含所有大于基准值的元素,并且将基准值放置到它在最终有序数组中的正确位置。本文将介绍一种常见的就地分区策略,该策略利用双指针技术实现。

分区函数详解:getPivotIndex

getPivotIndex 函数负责执行实际的分区操作,并返回基准值在排序后子数组中的最终索引。

  1. 基准值选择:在本实现中,我们简单地选择当前子数组的第一个元素 arg[startIndex] 作为基准值 (pivotVal)。虽然这种选择方式简单,但在某些情况下(如数组已部分有序或逆序),可能会导致性能下降。
  2. 双指针初始化
    • 左指针 i 初始化为 startIndex。
    • 右指针 j 初始化为 endIndex(注意:endIndex 是不包含的边界)。
  3. 遍历与交换逻辑: while (i
  4. 右指针 j 移动:while (i = pivotVal));
    • 右指针 j 从右向左移动(先自减),寻找第一个小于 pivotVal 的元素。
    • 如果找到一个小于 pivotVal 的元素,并且 i
  5. 左指针 i 移动:while (i
  6. 左指针 i 从左向右移动(先自增),寻找第一个大于 pivotVal 的元素。
  7. 如果找到一个大于 pivotVal 的元素,并且 i
  8. 这个过程持续进行,直到 i 和 j 相遇或交叉。当它们相遇时,i 和 j 指向的将是基准值最终的正确位置。
  9. 基准值归位:循环结束后,i 和 j 相遇的位置就是基准值最终的正确位置。我们将最初选定的 pivotVal 放置于 arg[j]。
  10. 返回基准值索引:函数返回 j 作为基准值的最终索引。此时,arg[j] 左侧的所有元素都小于或等于 pivotVal,右侧的所有元素都大于或等于 pivotVal。

主排序函数:quickSort

quickSort 函数是快速排序的入口点,它负责处理递归调用。

RecoveryFox AI
RecoveryFox AI

AI驱动的数据恢复、文件恢复工具

下载
  1. 递归基线条件:if (endIndex - startIndex
  2. 当子数组的长度小于2时(即子数组只包含0个或1个元素),表示子数组已经有序,无法再进行分区。这是递归的终止条件,直接返回。
  3. 分区调用:int pivotIndex = getPivotIndex(arg, startIndex, endIndex);
    • 调用 getPivotIndex 函数,对当前子数组进行分区操作,并获取基准值在数组中的最终位置索引。
  4. 递归调用
    • quickSort(arg, startIndex, pivotIndex);:对基准值左侧的子数组(从 startIndex 到 pivotIndex,不包含 pivotIndex 处的基准值)进行递归排序。
    • quickSort(arg, pivotIndex + 1, endIndex);:对基准值右侧的子数组(从 pivotIndex + 1 到 endIndex,不包含 endIndex)进行递归排序。

完整示例代码 (Java)

以下是上述快速排序算法的完整 Java 实现:

public class QuickSort_Impl {

    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();

        // 调用快速排序,对整个数组进行排序
        // endIndex 使用数组长度,因为它是排他性的
        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;

        // 调用分区函数,找到基准值的最终位置,并完成一次分区。
        int pivotIndex = getPivotIndex(arg, startIndex, endIndex);

        // 递归排序基准值左侧的子数组。
        // 范围从 startIndex 到 pivotIndex (不包含 pivotIndex)。
        quickSort(arg, startIndex, pivotIndex);

        // 递归排序基准值右侧的子数组。
        // 范围从 pivotIndex + 1 到 endIndex (不包含 endIndex)。
        quickSort(arg, pivotIndex + 1, endIndex);
    }

    /**
     * 执行分区操作,并将基准值放置到其最终的排序位置。
     * @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;   // 右指针

        // 当左指针 i 小于右指针 j 时,持续进行分区操作。
        while (i < j) {
            // 右指针 j 从右向左移动,寻找第一个小于基准值的元素。
            // 注意:j 先自减,确保 j 指向有效元素。
            while (i < j && (arg[--j] >= pivotVal));
            // 如果找到,将该元素移动到 i 指向的位置。
            if (i < j)
                arg[i] = arg[j];

            // 左指针 i 从左向右移动,寻找第一个大于基准值的元素。
            // 注意:i 先自增,确保 i 指向有效元素。
            while (i < j && (arg[++i] <= pivotVal));
            // 如果找到,将该元素移动到 j 指向的位置。
            if (i < j)
                arg[j] = arg[i];

        } // End Outer while

        // 循环结束后,i 和 j 相遇,该位置就是基准值最终的排序位置。
        // 将基准值放置到这个位置。
        arg[j] = pivotVal;

        // 返回基准值的最终索引。
        return j;
    }
}

性能分析与注意事项

  1. 时间复杂度
    • 平均情况:O(n log n)。这是快速排序最常见的性能表现,非常高效。
    • 最坏情况:O(n^2)。当基准值选择不当,导致每次分区都产生一个空子数组(例如,每次都选择最大或最小元素作为基准值,而数组已经有序或逆序),此时快速排序的性能会退化为类似于冒泡排序
  2. 空间复杂度
    • 平均情况:O(log n)。这主要取决于递归调用的深度。
    • 最坏情况:O(n)。在最坏情况下,递归深度可能达到 n。
  3. 基准值选择
    • 基准值的选择对快速排序的性能至关重要。本文示例中选择第一个元素作为基准值,这在某些特定输入下可能导致最坏情况。
    • 优化策略包括:
      • 随机选择:随机选择一个元素作为基准值,可以有效避免最坏情况的发生。
      • 三数取中法:选择子数组的第一个、中间和最后一个元素,取它们的中位数作为基准值,这通常能更好地选择一个“中间”的基准值。
  4. 稳定性:快速排序通常不是稳定排序算法。这意味着具有相同值的元素的相对顺序在排序后可能发生改变。
  5. 与Lomuto/Hoare分区对比
    • 本文实现的这种分区策略是快速排序中常见且高效的一种,其核心是将基准值放置到最终位置。它与Lomuto分区法有异曲同工之处,都是在单次遍历中完成分区并确定基准位置。
    • Hoare分区法是另一种经典的分区方案,它通常被认为在实践中比Lomuto分区法更高效,因为它交换的次数更少。Hoare分区法不保证基准值最终位于其精确的排序位置,但它保证基准值左侧的元素都小于或等于基准值,右侧的元素都大于或等于基准值。

总结

快速排序是一种功能强大且广泛应用的排序

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

777

2023.08.22

sort排序函数用法
sort排序函数用法

sort排序函数的用法:1、对列表进行排序,默认情况下,sort函数按升序排序,因此最终输出的结果是按从小到大的顺序排列的;2、对元组进行排序,默认情况下,sort函数按元素的大小进行排序,因此最终输出的结果是按从小到大的顺序排列的;3、对字典进行排序,由于字典是无序的,因此排序后的结果仍然是原来的字典,使用一个lambda表达式作为key参数的值,用于指定排序的依据。

391

2023.09.04

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

94

2023.09.25

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

443

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

544

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

93

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

197

2025.08.29

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

396

2023.07.18

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

158

2026.01.28

热门下载

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

精品课程

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

共23课时 | 3万人学习

C# 教程
C# 教程

共94课时 | 7.8万人学习

Java 教程
Java 教程

共578课时 | 52.6万人学习

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

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