当前位置: 首页 > java >正文

【算法基础】快速排序算法 - JAVA

一、算法基础

1.1 什么是快速排序

快速排序(Quick Sort)是一种高效的分治排序算法,由英国计算机科学家Tony Hoare于1960年提出。它的核心思想是:

  • 选择一个基准元素(pivot)
  • 将数组分成两部分:小于基准的元素和大于基准的元素
  • 递归地对这两部分进行排序

快速排序是实际应用中最常用的排序算法之一,平均情况下时间复杂度为O(n log n),空间复杂度为O(log n)

1.2 快速排序的基本思想

  1. 选择基准:从数组中选择一个元素作为基准(通常是第一个元素、最后一个元素或中间元素)
  2. 分区操作:将数组中小于基准的元素放在基准的左边,大于基准的元素放在右边
  3. 递归排序:对基准左右两部分分别进行递归排序

这个过程被称为分区(Partition),是快速排序的核心操作。

1.3 时间复杂度分析

  • 最佳情况:O(n log n) —— 每次分区操作都将数组均匀地分成两部分
  • 平均情况:O(n log n) —— 大多数情况下的性能表现
  • 最差情况:O(n²) —— 当数组已经有序或几乎有序时,选择第一个或最后一个元素作为基准

二、快速排序的实现

2.1 基本实现

public class QuickSort {public static void sort(int[] arr) {if (arr == null || arr.length <= 1) {return;}quickSort(arr, 0, arr.length - 1);}private static void quickSort(int[] arr, int left, int right) {if (left < right) {// 获取分区点int pivotIndex = partition(arr, left, right);// 递归排序左半部分quickSort(arr, left, pivotIndex - 1);// 递归排序右半部分quickSort(arr, pivotIndex + 1, right);}}private static int partition(int[] arr, int left, int right) {// 选择最右边的元素作为基准int pivot = arr[right];// i是小于基准区域的边界int i = left - 1;// 遍历区间,将小于基准的元素放到左边for (int j = left; j < right; j++) {if (arr[j] <= pivot) {i++;// 交换元素swap(arr, i, j);}}// 将基准元素放到正确的位置swap(arr, i + 1, right);// 返回基准元素的索引return i + 1;}private static void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}
}

2.2 递归过程分析

假设有数组:[8, 4, 2, 9, 5, 7, 6, 1, 3]

  1. 第一次分区

    • 选择基准值 3(最后一个元素)
    • 分区后:[1, 2, 3, 9, 5, 7, 6, 8, 4]
    • 基准索引:2
  2. 左子数组递归:[1, 2]

    • 选择基准值 2
    • 分区后:[1, 2]
    • 基准索引:1
  3. 右子数组递归:[9, 5, 7, 6, 8, 4]

    • 选择基准值 4
    • 分区后:[4, 5, 7, 6, 8, 9]
    • 基准索引:0
  4. 继续递归...

最终得到排序结果:[1, 2, 3, 4, 5, 6, 7, 8, 9]

三、快速排序的优化

3.1 基准选择优化

选择好的基准可以显著提高快速排序的性能。最常用的优化方法是三数取中(Median-of-Three):

private static int selectPivot(int[] arr, int left, int right) {int mid = left + (right - left) / 2;// 将三个元素排序if (arr[left] > arr[mid]) {swap(arr, left, mid);}if (arr[left] > arr[right]) {swap(arr, left, right);}if (arr[mid] > arr[right]) {swap(arr, mid, right);}// 将中间值(基准)交换到right-1位置swap(arr, mid, right - 1);return arr[right - 1];
}

3.2 小数组使用插入排序

对于小规模数组(通常小于10个元素),插入排序比快速排序更高效:

private static void quickSort(int[] arr, int left, int right) {// 小数组使用插入排序if (right - left <= 10) {insertionSort(arr, left, right);return;}// 正常的快速排序过程if (left < right) {int pivotIndex = partition(arr, left, right);quickSort(arr, left, pivotIndex - 1);quickSort(arr, pivotIndex + 1, right);}
}private static void insertionSort(int[] arr, int left, int right) {for (int i = left + 1; i <= right; i++) {int key = arr[i];int j = i - 1;while (j >= left && arr[j] > key) {arr[j + 1] = arr[j];j--;}arr[j + 1] = key;}
}

3.3 尾递归优化

通过将递归调用替换为迭代,可以避免栈溢出:

private static void quickSort(int[] arr, int left, int right) {while (left < right) {int pivotIndex = partition(arr, left, right);// 递归处理较小的子数组,迭代处理较大的子数组if (pivotIndex - left < right - pivotIndex) {quickSort(arr, left, pivotIndex - 1);left = pivotIndex + 1;} else {quickSort(arr, pivotIndex + 1, right);right = pivotIndex - 1;}}
}

四、典型应用:荷兰国旗问题

荷兰国旗问题是快速排序的一个典型应用,它要求将数组分成三个部分:小于、等于、大于某个值的元素。这种分区方法也称为三向切分

4.1 问题描述

给定一个数组和一个基准值,将数组重新排列,使得所有小于基准的元素在左边,等于基准的元素在中间,大于基准的元素在右边。

4.2 解法实现

public static void dutchFlagPartition(int[] arr, int pivot) {int left = 0;       // 小于pivot的区域右边界int current = 0;    // 当前处理的元素int right = arr.length - 1;  // 大于pivot的区域左边界while (current <= right) {if (arr[current] < pivot) {// 小于pivot的元素放左边swap(arr, left, current);left++;current++;} else if (arr[current] > pivot) {// 大于pivot的元素放右边swap(arr, current, right);right--;// 注意:此时不增加current,因为交换来的元素还未处理} else {// 等于pivot的元素保持原位current++;}}
}

4.3 应用到快速排序

使用三向切分可以优化快速排序,特别是处理有大量重复元素的数组:

private static void quickSort3Way(int[] arr, int left, int right) {if (left >= right) {return;}// 记录小于、等于、大于pivot的三个区域边界int lt = left;      // 小于pivot的区域右边界int gt = right;     // 大于pivot的区域左边界int i = left + 1;   // 当前处理的元素int pivot = arr[left];while (i <= gt) {if (arr[i] < pivot) {swap(arr, lt++, i++);} else if (arr[i] > pivot) {swap(arr, i, gt--);} else {i++;}}// 递归处理小于和大于的部分quickSort3Way(arr, left, lt - 1);quickSort3Way(arr, gt + 1, right);
}

五、完整实现与示例

以下是一个包含各种优化的完整快速排序实现:

public class OptimizedQuickSort {private static final int INSERTION_SORT_THRESHOLD = 10;public static void sort(int[] arr) {if (arr == null || arr.length <= 1) {return;}quickSort(arr, 0, arr.length - 1);}private static void quickSort(int[] arr, int left, int right) {// 小数组使用插入排序if (right - left <= INSERTION_SORT_THRESHOLD) {insertionSort(arr, left, right);return;}// 使用三数取中法选择基准medianOfThree(arr, left, right);int pivot = arr[right - 1];// 分区int i = left;int j = right - 1;for (;;) {while (arr[++i] < pivot) {}while (arr[--j] > pivot) {}if (i >= j) break;swap(arr, i, j);}// 将基准放回正确位置swap(arr, i, right - 1);// 递归排序quickSort(arr, left, i - 1);quickSort(arr, i + 1, right);}private static void medianOfThree(int[] arr, int left, int right) {int mid = left + (right - left) / 2;if (arr[left] > arr[mid]) {swap(arr, left, mid);}if (arr[left] > arr[right]) {swap(arr, left, right);}if (arr[mid] > arr[right]) {swap(arr, mid, right);}// 将基准(中间值)放到right-1位置swap(arr, mid, right - 1);}private static void insertionSort(int[] arr, int left, int right) {for (int i = left + 1; i <= right; i++) {int key = arr[i];int j = i - 1;while (j >= left && arr[j] > key) {arr[j + 1] = arr[j];j--;}arr[j + 1] = key;}}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) {int[] arr = {8, 4, 2, 9, 5, 7, 6, 1, 3};System.out.println("原始数组: " + Arrays.toString(arr));sort(arr);System.out.println("排序后数组: " + Arrays.toString(arr));}
}

六、总结

核心要点

  1. 分治思想:快速排序采用分治策略,通过分区操作将问题分解为子问题
  2. 基准选择:基准元素的选择直接影响算法效率,好的选择可以避免最坏情况
  3. 就地排序:快速排序是一种原地排序算法,不需要额外的数组空间
  4. 不稳定性:快速排序不是稳定排序算法,相同元素的相对顺序可能改变

优缺点

优点:

  • 平均情况下,性能优于其他 O(n log n) 排序算法
  • 不需要额外的存储空间(原地排序)
  • 对缓存友好,局部性好

缺点:

  • 最坏情况下,时间复杂度为 O(n²)
  • 不稳定排序,无法保持相等元素的相对顺序
  • 对于小数组,可能不如插入排序高效

适用场景

  • 需要高效排序大数组时
  • 内存受限、不能使用额外空间时
  • 当平均性能比最坏情况性能更重要时
  • 不要求排序稳定性时

快速排序是一种高效且实用的排序算法,在大多数情况下都表现出色。通过本文介绍的各种优化技巧,可以使快速排序在实际应用中获得最佳性能。掌握快速排序是每位程序员的基本功,也是理解分治算法思想的重要一步。

http://www.xdnf.cn/news/3654.html

相关文章:

  • Cycleresearcher:通过自动化评审改进自动化研究
  • Python 数据智能实战 (10):智能商品推荐 - LLM “猜你喜欢”
  • v0.6.7/OllamaSetup.exe下载链接
  • SpringSecurity配置(权限认证)
  • 论数据分片技术及其应用
  • 市面上所有大模型apikey获取指南(持续更新中)
  • 进程间通信(IPC)
  • 安卓基础(悬浮窗和摄像)
  • 基于大模型的肾结石诊疗全流程风险预测与方案制定研究报告
  • Oracle无法正常OPEN(四)
  • Spring AI 实战:第一章、Spring AI入门之DeepSeek调用
  • 天翼云ftp服务器搭建详细步骤,ftp服务器路径怎么写?
  • Centos9 安装 RocketMQ5
  • WebSocket分布式实现方案
  • MySQL中的窗口函数
  • Modbus 通讯协议(超详细,简单易懂)
  • Qt 中实现观察者模式(Observer Pattern)
  • Milvus(12):分析器
  • 虚拟机软件详解
  • AI日报 · 2025年5月03日|Perplexity 集成 WhatsApp,苹果传与 Anthropic 合作开发 Xcode
  • 青少年编程与数学 02-018 C++数据结构与算法 24课题、密码学算法
  • 【C#】一个类中的接口方法使用static和不使用static的区别
  • aidermacs开源程序使用 Aider 在 Emacs 中进行 AI 配对编程
  • 使用xlwings将excel表中将无规律的文本型数字批量转化成真正的数字
  • 自定义Dockerfile,发布springboot项目
  • Mysql进阶篇1_存储引擎、索引、SQL性能分析指令
  • 基于Jenkins的DevOps工程实践之Jenkins共享库
  • AVIOContext 再学习
  • Spring 容器相关的核心注解​
  • 19. LangChain安全与伦理:如何避免模型“幻觉“与数据泄露?