【算法基础】快速排序算法 - 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.mzph.cn/news/904070.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux用户管理命令和用户组管理命令

一、用户管理命令 1.1、adduser 添加新用户 1、基本语法 adduser 用户名 &#xff08;功能描述&#xff1a;添加新用户&#xff09; 应用场景1&#xff1a;企业开发&#xff0c;多人协同&#xff08;也会有多人使用相同的一个低权限用户&#xff09;。 应用场景2&#x…

记录两个免费开源又好用的后台模版vue3

一.element-plus-admin 一套基于vue3、element-plus、typesScript、vite的后台集成方案 1.简介 vue-element-plus-admin 是一个基于 element-plus 免费开源的中后台模版。使用了最新的 Vue3&#xff0c;Vite&#xff0c;Typescript等主流技术开发&#xff0c;开箱即用的中后…

Flip PDF Plus Corp7.7.22电子书制作软件

flip pdf plus corporate7.7.22中文版由FlipBuilder官方出品的一款企业级的翻页电子书制作软件&#xff0c;拥有丰富的模板&#xff0c;主题和动画场景&#xff0c;每本书最大页数1000页&#xff0c;每本书的最大大小1GB&#xff0c;即可以帮助企业用户制作好丰富的电子书籍。 …

C语言蓝桥杯真题代码

以下是不同届蓝桥杯C语言真题代码示例&#xff0c;供参考&#xff1a; 第十三届蓝桥杯省赛 C语言大学B组 真题&#xff1a;卡片 题目&#xff1a;小蓝有很多数字卡片&#xff0c;每张卡片上都是数字1-9。他想拼出1到n的数列&#xff0c;每张卡片只能用一次&#xff0c;求最大的…

[Windows] Kazumi番剧采集v1.6.9:支持自定义规则+在线观看+弹幕,跨平台下载

[Windows] Kazumi番剧采集 链接&#xff1a;https://pan.xunlei.com/s/VOPLMhEQD7qixvAnoy73NUK9A1?pwdtu6i# Kazumi是一款基于框架; 开发的轻量级番剧采集工具&#xff0c;专为ACG爱好者设计。通过;自定义XPath规则; 实现精准内容抓取&#xff0c;支持多平台&#xff08;An…

探秘数据结构:构建高效算法的灵魂密码

摘要 数据结构作为计算机科学的基石&#xff0c;其设计与优化直接影响算法效率、资源利用和系统可靠性。本文系统阐述数据结构的基础理论、分类及其核心操作&#xff0c;涵盖数组、链表、栈、队列、树、图、哈希表与堆等经典类型。深入探讨各结构的应用场景与性能对比&#xf…

机器人--架构及设备

机器人的四大组成部分 控制系统 驱控系统 执行系统 电机属于执行系统的设备。 传感系统 传感系统分为内部传感系统和外部传感系统。 内部传感系统(内部传感器)&#xff1a; 用于获取机器人内部信息&#xff0c;比如IMU&#xff0c;力传感器等。 外部传感系统(外部传感器):…

人工智能:如何快速筛选出excel中某列存在跳号的单元格位置?

前提&#xff1a; 电脑上必须提前安装好了【office AI】软件工具 方法如下&#xff1a; 1、打开要操作的excel表格&#xff0c;点击上方的【officeAI】&#xff0c;再点击左边的【右侧面板】按钮&#xff0c;就会出现如下右侧的【OfficeAI助手】 2、在OfficeAI助手的聊天框…

Spring MVC入门

介绍了Spring MVC框架的概念、特征及核心功能&#xff0c;通过案例详细介绍了Spring MVC开发所需要的开发环境以及基本的开发步骤。 一、Spring MVC框架概述 Spring MVC是Spring框架的一个模块&#xff0c;是一个基于Java的实现了MVC设计模式的轻量级Web框架。它通过一套注解和…

贪心算法求解边界最大数

贪心算法求解边界最大数&#xff08;拼多多2504、排列问题&#xff09; 多多有两个仅由正整数构成的数列 s1 和 s2&#xff0c;多多可以对 s1 进行任意次操作&#xff0c;每次操作可以置换 s1 中任意两个数字的位置。多多想让数列 s1 构成的数字尽可能大&#xff0c;但是不能比…

Ubuntu ZLMediakit的标准配置文件(rtsp->rtmp->hls)

最近在工作中遇到不生成hls资源的问题,后面发现是配置文件有误,特此记录正确的config.ini配置文件,方便查阅。 最终解决方案,通过下面这种格式可以访问到flv视频,具体为什么不太清楚,rtmp格式:rtmp://39.113.48.113:8089/live/1744168516937396175 记录最终解决方案:ht…

# LeetCode 1007 行相等的最少多米诺旋转

LeetCode 1007 行相等的最少多米诺旋转 原题英文&#xff1a;Minimum Domino Rotations For Equal Row 难度&#xff1a;中等 | 标签&#xff1a;数组、贪心 1 题目重述 给定两行长度相同的多米诺骨牌&#xff1a; tops[i] 表示第 i 张骨牌上面的数字&#xff1b;bottoms[…

大数据技术:从趋势到变革的全景探索

📝个人主页🌹:一ge科研小菜鸡-CSDN博客 🌹🌹期待您的关注 🌹🌹 在数字化时代的浪潮下,大数据已经不再是一个陌生的概念。从日常生活中的社交媒体,到企业决策支持系统,再到公共管理的大数据应用,它正在改变着我们的工作和生活方式。随着技术的进步,传统的数据…

前端八股Day5——XHS某中厂实习前端一面

没写完&#xff0c;睡醒补 CSS盒模型 //出现频率好高&#xff0c;感觉每次写面经都遇到 W3C标准盒模型(content-box)&#xff1a;盒子宽高width/heightpaddingbordermargin IE怪异盒模型(border-box)&#xff1a;盒子宽高width/heigth(包括padding和border)margin 默认标准切换…

INP指标

什么是INP&#xff08;Interaction to Next Paint&#xff09; 参考网站&#xff1a;webVital-INP文档 定义与核心目标 INP 是一项稳定的 Core Web Vitals 指标&#xff0c;通过统计用户访问期间所有符合条件的互动约定时间&#xff0c;评估网页对用户操作的总体响应能力。最…

剖析扩散模型(Denoising Diffusion Probabilistic Models)

文章目录 1. 前言2. 前向扩散过程(Forward Diffusion)3. 反向生成过程&#xff08;Reverse Process&#xff09;4. 训练和推理过程中的伪代码5. 训练过程代码实现&#xff08;Training&#xff09;5.1 时间嵌入模块——TimeEmbedding5.2 前向扩散过程——GaussianDiffusionTrai…

基于 Spring Boot 瑞吉外卖系统开发(九)

基于 Spring Boot 瑞吉外卖系统开发&#xff08;九&#xff09; 保存菜品 菜品管理页面提供了一个“新增菜品”按钮&#xff0c;单击该按钮时&#xff0c;会打开新增菜品页面。 请求路径/dish&#xff0c;请求方法POST&#xff0c;参数使用DishDto类接收。 DishDto 添加f…

w317汽车维修预约服务系统设计与实现

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…

【Agent搭建】利用coze平台搭建一个AI销售?

目录 一、关于coze 核心功能 二、搭建属于你自己智能体 备注&#xff1a;&#xff08;以下说明比较需要调整的板块&#xff09; 1、从Prompt工程开始 2、搭建工作流 3、添加知识 三、总结 一、关于coze Coze是字节跳动推出的AI应用开发平台&#xff0c;专注于帮助用户快速…

Sharding-JDBC分库分表中的热点数据分布不均匀问题及解决方案

引言 在现代分布式应用中&#xff0c;使用Sharding-JDBC进行数据库的分库分表是提高系统性能和扩展性的常见策略。然而&#xff0c;在实际应用中&#xff0c;某些特定的数据&#xff08;如最新订单、热门商品等&#xff09;可能会成为“热点”&#xff0c;导致这些部分的数据处…