news 2026/4/18 11:53:32

算法系列(Algorithm)- 快速排序

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
算法系列(Algorithm)- 快速排序

1. 基本思想与核心原理

快速排序的核心思想是通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

该算法的基本步骤包括:

  1. 选择基准值(Pivot):从数组中选择一个元素作为基准值
  2. 分区操作(Partition):重新排列数组,使所有小于基准值的元素都放在基准值的左边,所有大于基准值的元素都放在基准值的右边
  3. 递归排序:对基准值左右两边的子数组递归地执行快速排序

2. 快速排序的详细实现步骤

2.1 分区操作详解

分区操作是快速排序算法的核心部分,其目标是将数组重新排列,使得所有小于基准值的元素位于基准值的左侧,所有大于基准值的元素位于基准值的右侧。

分区算法的具体过程

  1. 选择数组的最后一个元素作为基准值(pivot)
  2. 初始化两个指针:i指向较小元素的边界(初始为low-1),j用于遍历数组
  3. 遍历数组从low到high-1:
    • 如果当前元素小于等于基准值,将i指针右移一位,然后交换arr[i]和arr[j]
  4. 最后将基准值放到正确位置(i+1处)

2.2 递归排序过程

完成分区操作后,基准值已经处于其最终排序位置。此时,数组被分成两个子数组:

  • 左子数组:包含所有小于基准值的元素
  • 右子数组:包含所有大于基准值的元素

对这两个子数组分别递归地执行快速排序,直到子数组的大小为0或1(即已经有序)。

2.3 打个比喻

可以把快速排序想象成快速分类快递

  1. 选一个参考快递:比如选一个重量为1kg的快递作为参考
  2. 快速分堆:把所有小于1kg的放左边,大于1kg的放右边
  3. 每堆再分:对左边那堆,再选一个0.5kg的作为参考,继续分
  4. 直到分完:一直分到每堆只有一个快递,就全部排好序了

这个方法的聪明之处在于:不用一个个比较所有快递,而是通过选参考值快速分成两堆,大大减少了比较次数。

3. Java实现完整代码

以下是快速排序在Java中的标准实现,包含详细的注释说明:

import java.util.Arrays; public class QuickSort { /** * 快速排序的入口方法 * @param arr 待排序的数组 */ public static void quickSort(int[] arr) { if (arr == null || arr.length == 0) { return; } quickSort(arr, 0, arr.length - 1); } /** * 递归实现快速排序 * @param arr 待排序的数组 * @param low 子数组的起始索引 * @param high 子数组的结束索引 */ private static void quickSort(int[] arr, int low, int high) { if (low < high) { // 执行分区操作,获取基准值的正确位置 int pivotIndex = partition(arr, low, high); // 递归排序左子数组(基准值左边的元素) quickSort(arr, low, pivotIndex - 1); // 递归排序右子数组(基准值右边的元素) quickSort(arr, pivotIndex + 1, high); } } /** * 分区操作 - 快速排序的核心 * @param arr 待分区的数组 * @param low 分区起始索引 * @param high 分区结束索引 * @return 基准值的最终位置索引 */ private static int partition(int[] arr, int low, int high) { // 选择最后一个元素作为基准值(pivot) int pivot = arr[high]; // i指向较小元素的边界,初始为low-1 int i = low - 1; // 遍历数组,将小于基准值的元素移到左边 for (int j = low; j < high; j++) { if (arr[j] <= pivot) { i++; swap(arr, i, j); } } // 将基准值放到正确的位置(i+1) swap(arr, i + 1, high); // 返回基准值的最终位置 return i + 1; } /** * 交换数组中两个元素的位置 * @param arr 数组 * @param i 第一个元素的索引 * @param j 第二个元素的索引 */ private static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } /** * 测试方法 */ public static void main(String[] args) { // 测试用例1:普通数组 int[] arr1 = {10, 7, 8, 9, 1, 5}; System.out.println("原始数组1: " + Arrays.toString(arr1)); quickSort(arr1); System.out.println("排序后数组1: " + Arrays.toString(arr1)); // 测试用例2:包含重复元素的数组 int[] arr2 = {3, 6, 8, 10, 1, 2, 1}; System.out.println("\n原始数组2: " + Arrays.toString(arr2)); quickSort(arr2); System.out.println("排序后数组2: " + Arrays.toString(arr2)); // 测试用例3:已排序数组 int[] arr3 = {1, 2, 3, 4, 5}; System.out.println("\n原始数组3: " + Arrays.toString(arr3)); quickSort(arr3); System.out.println("排序后数组3: " + Arrays.toString(arr3)); // 测试用例4:逆序数组 int[] arr4 = {5, 4, 3, 2, 1}; System.out.println("\n原始数组4: " + Arrays.toString(arr4)); quickSort(arr4); System.out.println("排序后数组4: " + Arrays.toString(arr4)); } }

4. 算法性能分析

4.1 时间复杂度分析

快速排序的时间复杂度取决于分区操作的质量:

  1. 最佳情况:每次分区都能将数组均匀分成两半,此时时间复杂度为O(n log n)
  2. 平均情况:在随机数据下,快速排序的平均时间复杂度也为O(n log n),这是其在实际应用中表现优异的主要原因
  3. 最坏情况:当每次分区都产生极度不平衡的分区时(例如数组已经有序或逆序),时间复杂度会退化到O(n²)

4.2 空间复杂度分析

快速排序是原地排序算法,不需要额外的存储空间来存储数据副本。其空间复杂度主要来自递归调用栈:

  • 最佳和平均情况:O(log n)(递归深度)
  • 最坏情况:O(n)(递归深度达到n)

4.3 稳定性分析

快速排序是一种不稳定的排序算法。在分区过程中,相等元素的相对位置可能会发生变化。

5. 快速排序的优化策略

虽然基本的快速排序算法已经相当高效,但在实际应用中还可以通过以下策略进行优化:

5.1 基准值选择优化

  1. 随机选择基准值:通过随机选择基准值,可以避免最坏情况的发生,提高算法的平均性能
  2. 三数取中法:选择数组的第一个、中间和最后一个元素的中位数作为基准值,可以减少分区不平衡的情况
  3. 随机化快速排序:在分区前随机交换一个元素到末尾作为基准值

5.2 小数组优化

当子数组的大小小于某个阈值(通常为10-20)时,可以使用插入排序代替快速排序。因为对于小数组,插入排序的常数因子更小,实际运行速度更快。

5.3 三路快速排序

对于包含大量重复元素的数组,可以使用三路快速排序(Three-way QuickSort)。它将数组分成三部分:小于基准值、等于基准值和大于基准值。这样可以避免对相等元素的重复比较和交换。

三路快速排序的实现示例:

public static void threeWayQuickSort(int[] arr, int low, int high) { if (low < high) { int[] pivotRange = threeWayPartition(arr, low, high); threeWayQuickSort(arr, low, pivotRange[0] - 1); threeWayQuickSort(arr, pivotRange[1] + 1, high); } }

6. 快速排序的应用场景

快速排序因其高效性而被广泛应用于各种场景:

  1. 大规模数据排序:快速排序的平均时间复杂度为O(n log n),在处理大规模数据时表现优异
  2. 数据库查询优化:在数据库系统中,快速排序常用于优化查询性能,特别是对需要排序的大量记录进行排序
  3. 数据分析与处理:在数据分析和统计领域,快速排序被用来快速对数据进行排序,以便进行后续的分析和处理
  4. 编程语言内置排序:许多编程语言的标准库中的排序函数都基于快速排序或其变体实现

7. 与其他排序算法的比较

7.1 与插入排序比较

  • 对于小规模数据(n < 20),插入排序通常更快
  • 快速排序在大规模数据上优势明显
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 6:33:30

对上篇二分查找的纠正和补充

1.上篇提到无序数组用sort排序再二分&#xff0c;感觉被自己蠢笑了&#xff0c;因为sort函数的时间复杂度O(nlogn),二分查找是(logn),所以这个是无意义的。然后上一篇sort函数用法也写错了&#xff0c;应该是sort(a1, a n1)&#xff0c;因为初始下标为1&#xff0c;注意一下就…

作者头像 李华
网站建设 2026/4/18 6:34:19

5分钟掌握AutoHotkey:打造专属自动化神器

5分钟掌握AutoHotkey&#xff1a;打造专属自动化神器 【免费下载链接】AutoHotkey 项目地址: https://gitcode.com/gh_mirrors/autohotke/AutoHotkey 你是否厌倦了日复一日的重复点击操作&#xff1f;想要一键完成复杂的鼠标任务&#xff1f;AutoHotkey这款强大的自动化…

作者头像 李华
网站建设 2026/4/18 6:36:23

RaceControl终极指南:告别F1TV官方客户端的所有痛点

RaceControl终极指南&#xff1a;告别F1TV官方客户端的所有痛点 【免费下载链接】RaceControl Race Control is a standalone, open source F1TV client for Windows, written in C# on the .NET platform. 项目地址: https://gitcode.com/gh_mirrors/ra/RaceControl 还…

作者头像 李华
网站建设 2026/4/18 7:00:08

从碎片化日志到全景洞察:ZincObserve跨数据源关联查询实战指南

在当今复杂的云原生环境中&#xff0c;系统产生的日志数据如同散落在沙滩上的贝壳&#xff0c;看似零散却蕴含着宝贵的业务洞察。传统的日志分析工具往往只能提供单维度的查询能力&#xff0c;难以将不同来源的数据关联起来形成完整的业务视图。ZincObserve作为新一代可观测性平…

作者头像 李华
网站建设 2026/4/18 7:03:08

【深度好文】大模型微调技术详解:从原理到实践(建议收藏)

文章系统介绍了大模型微调技术的发展历程、核心价值及主流技术方案。从AI发展的四个阶段演进到大语言模型&#xff0c;详细分析了大模型需要微调的原因&#xff08;预训练成本高、提示工程局限等&#xff09;&#xff0c;并重点解析了PEFT技术路线&#xff0c;包括Prompt Tunin…

作者头像 李华
网站建设 2026/4/18 7:00:19

55、用 SQL 管理数据

用 SQL 管理数据 1. 挑选 SQL 包 SQL 是一种用于访问数据的语言,而特定的 SQL 包则实现了这门语言。这类似于网络协议(如 SMTP)和实现该协议的服务器(如 sendmail、Postfix 和 Exim)之间的关系。理论上,你可以使用任何 SQL 包来满足 SQL 数据库需求,但实际上,使用 SQ…

作者头像 李华