【算法系列】快速排序详解
文章目录
- 快速排序的多种实现方式
- 1. 基本快速排序(Lomuto 分区方案)
- 1.1 基本原理
- 1.2 步骤
- 1.3 Java 实现示例
- 2. Hoare 分区方案
- 2.1 基本原理
- 2.2 步骤
- 2.3 Java 实现示例
- 3. 三数取中法
- 3.1 基本原理
- 3.2 步骤
- 3.3 Java 实现示例
- 4. 尾递归优化
- 4.1 基本原理
- 4.2 步骤
- 4.3 Java 实现示例
- 总结
快速排序的多种实现方式
快速排序(Quick Sort)是一种高效的排序算法,采用分治法策略。它通过选择一个“基准”元素,将数组分割成两个子数组,并递归地对这两个子数组进行排序。本文将详细介绍几种常见的快速排序实现方式,并讨论它们的特点和适用场景。

1. 基本快速排序(Lomuto 分区方案)
1.1 基本原理
Lomuto 分区方案是最常见的快速排序实现之一。它选择数组的最后一个元素作为基准(pivot),然后重新排列数组,使得所有小于基准的元素位于基准的左侧,所有大于基准的元素位于基准的右侧。
1.2 步骤
- 选择基准:通常选择数组的最后一个元素作为基准。
- 初始化指针:设置一个指针
i,用于追踪当前小于基准的最后一个元素的位置。 - 遍历数组:
- 遍历数组中的每个元素(除了基准元素),如果当前元素小于等于基准,则将该元素与
i指针所指向的元素交换,并将i向右移动一位。
- 遍历数组中的每个元素(除了基准元素),如果当前元素小于等于基准,则将该元素与
- 放置基准:最后将基准元素与
i + 1位置的元素交换,使得基准元素处于正确的位置。 - 递归排序:对基准两侧的子数组分别递归执行上述过程,直到每个子数组只剩下一个元素或为空。
1.3 Java 实现示例
public static void quickSort(int[] arr) {quickSort(arr, 0, arr.length - 1);
}private static void quickSort(int[] arr, int low, int high) {if(low >= high) {return;}int pivotIndex = lomuto(arr, low, high);quickSort(arr, low, pivotIndex - 1);quickSort(arr, pivotIndex + 1, high);
}private static int lomuto(int[] arr, int low, int high) {int pivot = arr[high]; // 选择最后一个元素作为基准int i = low - 1; // 指向当前小于基准的最后一个元素for (int j = low; j < high; j ++) {if(arr[j] <= pivot) {i ++;if(i != j) {swap(arr, i, j);System.out.println(low + "|" + high + " " + i + "|" + j + " " + Arrays.toString(arr));}}}// 将基准元素放回数组正确位置swap(arr, i + 1, high);System.out.println(low + "|" + high + "\t \t" + Arrays.toString(arr));return i + 1;
}/*** 交换数组索引为i和j的两个元素* @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;
}
2. Hoare 分区方案
2.1 基本原理
Hoare 分区方案由 C.A.R. Hoare 提出,是另一种常见的快速排序实现。它通过选择一个基准元素(通常是第一个或最后一个元素),然后重新排列数组,使得所有小于基准的元素位于基准的左侧,所有大于基准的元素位于基准的右侧。
2.2 步骤
- 选择基准:通常选择数组的第一个元素作为基准。
- 初始化双指针:设置两个指针,分别指向数组的起始位置和结束位置。
- 移动指针:
- 左指针向右移动,直到找到一个大于基准的元素。
- 右指针向左移动,直到找到一个小于基准的元素。
- 交换元素:当左右指针都停止时,交换这两个元素的位置。
- 重复步骤3和4:继续移动指针并交换元素,直到左右指针相遇。
- 放置基准:最后将基准元素与右指针的位置交换,使得基准元素处于正确的位置。
2.3 Java 实现示例
public static void quickSort(int[] arr) {quickSort(arr, 0, arr.length - 1);
}private static void quickSort(int[] arr, int low, int high) {if(low >= high) {return;}int pivotIndex = hoare(arr, low, high);quickSort(arr, low, pivotIndex - 1);quickSort(arr, pivotIndex + 1, high);
}private static int hoare(int[] arr, int low, int high) {int pivot = arr[low]; // 选择一个元素作为基准int l = low; // 左索引int r = high; // 右索引while(l < r) {while(l < r && arr[r] > pivot) {r --;}while(l < r && arr[l] < pivot) {l ++;}if(l < r) {swap(arr, l, r);System.out.println(l + "|" + r + "\t\t" + Arrays.toString(arr));}}return l;
}/*** 交换数组索引为i和j的两个元素* @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;
}
3. 三数取中法
3.1 基本原理
为了减少快速排序在最坏情况下的时间复杂度(即 O(n^2)),可以使用“三数取中法”来选择基准元素。这种方法通过选择数组的第一个、中间和最后一个元素中的中位数作为基准,从而减少最坏情况的发生概率。
3.2 步骤
- 选择基准:选择数组的第一个、中间和最后一个元素中的中位数作为基准。
- 分区操作:根据选择的基准进行分区操作,可以使用 Lomuto 或 Hoare 分区方案。
- 递归排序:对基准两侧的子数组分别递归执行上述过程,直到每个子数组只剩下一个元素或为空。
3.3 Java 实现示例
public static void quickSort(int[] arr) {quickSort(arr, 0, arr.length - 1);
}private static void quickSort(int[] arr, int low, int high) {if (low >= high) {return;}int pivotIndex = lomuto(arr, low, high);quickSort(arr, low, pivotIndex - 1);quickSort(arr, pivotIndex + 1, high);
}private static int lomuto(int[] arr, int low, int high) {int pivotIndex = medianOfThree(arr, low, high); // 使用三数取中法选择基准int pivot = arr[pivotIndex];int i = low - 1; // 指向当前小于基准的最后一个元素if (pivotIndex != high) {swap(arr, pivotIndex, high); // 将基准元素移到最后System.out.println("pivot:" + pivotIndex + "|" + high + "\t" + Arrays.toString(arr));}for (int j = low; j < high; j++) {if (arr[j] <= pivot) {i++;if (i != j) {swap(arr, i, j);System.out.println(low + "|" + high + " \t" + i + "|" + j + "\t" + Arrays.toString(arr));}}}// 将基准元素放回数组正确位置if (i + 1 != high) {swap(arr, i + 1, high);System.out.println("back:" + (i + 1) + "|" + high + "\t" + Arrays.toString(arr));}return i + 1;
}/*** 选择数组的第一个、中间和最后一个元素中的中位数作为基准,返回其下标** @param arr* @param low* @param high* @return*/
private static int medianOfThree(int[] arr, int low, int high) {int mid = (low + high) / 2;if ((arr[low] <= arr[mid] && arr[mid] <= arr[high]) || (arr[low] >= arr[mid] && arr[mid] >= arr[high])) {return mid;}if ((arr[mid] <= arr[low] && arr[low] <= arr[high]) || (arr[mid] >= arr[low] && arr[low] >= arr[high])) {return low;}return high;
}/*** 交换数组索引为i和j的两个元素** @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;
}
4. 尾递归优化
4.1 基本原理
快速排序的递归实现可能导致栈溢出问题,特别是在处理大规模数据集时。为了避免这种情况,可以使用尾递归优化技术来减少递归调用栈的深度。
4.2 步骤
- 选择基准:选择数组的某个元素作为基准。
- 分区操作:根据选择的基准进行分区操作,可以使用 Lomuto 或 Hoare 分区方案。
- 尾递归优化:每次递归只对较小的子数组进行递归调用,较大的子数组则通过循环继续处理,从而减少递归调用栈的深度。
4.3 Java 实现示例
public static void quickSort(int[] arr) {quickSort(arr, 0, arr.length - 1);
}private static void quickSort(int[] arr, int low, int high) {// 使用循环代替递归while (low < high) {int pivotIndex = hoare(arr, low, high);// 对较小的分区进行递归调用if (pivotIndex - low < high - pivotIndex) {quickSort(arr, low, pivotIndex - 1);low = pivotIndex + 1;} else {quickSort(arr, pivotIndex + 1, high);high = pivotIndex - 1;}}
}private static int hoare(int[] arr, int low, int high) {int pivot = arr[low]; // 选择一个元素作为基准int l = low; // 左索引int r = high; // 右索引while (l < r) {while (l < r && arr[r] > pivot) {r--;}while (l < r && arr[l] < pivot) {l++;}if (l < r) {swap(arr, l, r);System.out.println(l + "|" + r + "\t\t" + Arrays.toString(arr));}}return l;
}/*** 交换数组索引为i和j的两个元素** @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;
}
总结
快速排序有多种实现方式,每种实现方式在不同的应用场景下可能有不同的性能表现:
- Lomuto 分区方案:简单易懂,但相比 Hoare 分区方案,在某些情况下可能会有更多的交换操作,导致效率稍低。
- Hoare 分区方案:通常比 Lomuto 分区更高效,因为它减少了不必要的交换操作,从而减少了时间复杂度。
- 三数取中法:通过选择数组的第一个、中间和最后一个元素中的中位数作为基准,减少最坏情况的发生概率。
- 尾递归优化:通过优化递归调用栈的深度,避免栈溢出问题,特别适合处理大规模数据集。
相关文章:
【算法系列】快速排序详解
文章目录 快速排序的多种实现方式1. 基本快速排序(Lomuto 分区方案)1.1 基本原理1.2 步骤1.3 Java 实现示例 2. Hoare 分区方案2.1 基本原理2.2 步骤2.3 Java 实现示例 3. 三数取中法3.1 基本原理3.2 步骤3.3 Java 实现示例 4. 尾递归优化4.1 基本原理4.…...
电脑键盘知识
1、键盘四大功能区 1. 功能区 2. 主要信息输入区 3. 编辑区 4. 数字键盘区 笔记本电脑键盘的功能区,使用前需先按Fn键 1.1、功能区 ESC:退出 F1:显示帮助信息 F2:重命名 F4:重复上一步操作 F5:刷新网页 …...
Grok 3 vs. DeepSeek vs. ChatGPT:2025终极AI对决
2025 年,AI 领域的竞争愈发激烈,三个重量级选手争夺霸主地位:Grok 3(由 xAI 开发)、DeepSeek(国内 AI 初创公司)和 ChatGPT(OpenAI 产品)。每个模型都有自己独特的优势,无论是在深度思考、速度、编程辅助、创意输出,还是在成本控制方面,都展现出强大的实力。但究竟…...
【MySQL篇】数据库基础
目录 1,什么是数据库? 2,主流数据库 3,MySQL介绍 1,MySQL架构 2,SQL分类 3,MySQL存储引擎 1,什么是数据库? 数据库(Database,简称DB…...
vscode java环境中文乱码的问题
先说我的结论: 由于我的系统是windows的,所以vscode使用的是默认gbk的编码进行的。 但是我的目的是全部都使用utf-8,因为我的程序始终是要去linux上去运行的,总不能在本地是好的,然后到服务器上就不行了吧,…...
基于SpringBoot+mybatisplus+vueJS的Cosplay文化展示与交流社区设计与实现
博主介绍:硕士研究生,专注于信息化技术领域开发与管理,会使用java、标准c/c等开发语言,以及毕业项目实战✌ 从事基于java BS架构、CS架构、c/c 编程工作近16年,拥有近12年的管理工作经验,拥有较丰富的技术架…...
组件传递props校验
注意:prop是只读的!不可以修改父组件的数据。 可以检验传过来的内容是否类型没问题。 App.vue <template><div><!-- <parentDemo/> --><componentA/></div></template> <script> import ComponentA …...
数据结构与算法-图论-最短路-拓展运用
选择最佳路线 分析: 这是一道图论中的最短路径问题,目标是在给定的公交网络中,找到从琪琪家附近的车站出发,到她朋友家附近车站(编号为 s )的最短时间。以下是对该问题的详细分析: 问题关键信息…...
0—QT ui界面一览
2025.2.26,感谢gpt4 1.控件盒子 1. Layouts(布局) 布局控件用于组织界面上的控件,确保它们的位置和排列方式合理。 Vertical Layout(垂直布局) :将控件按垂直方向排列。 建议:适…...
纷析云:赋能企业财务数字化转型的开源解决方案
在企业数字化转型的浪潮中,财务管理的高效与安全成为关键。纷析云凭借其开源、安全、灵活的财务软件解决方案,为企业提供了一条理想的转型路径。 一、开源的力量:自主、安全、高效 纷析云的核心优势在于其100%开源的财务软件源码。这意味着…...
P8716 [蓝桥杯 2020 省 AB2] 回文日期
1 题目说明 2 题目分析 暴力不会超时,O(n)的时间复杂度, < 1 0 8 <10^8 <108。分析见代码: #include<iostream> #include<string> using namespace std;int m[13]{0,31,28,31,30,31,30,31,31,30,31,30,31};// 判断日期…...
(十)趣学设计模式 之 外观模式!
目录 一、 啥是外观模式?二、 为什么要用外观模式?三、 外观模式的实现方式四、 外观模式的优缺点五、 外观模式的应用场景六、 总结 🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支…...
apache-maven-3.2.1
MAVEN_HOME D:\apache-maven-3.2.1 PATH D:\apache-maven-3.2.1\bin cmd mvn -v <localRepository>d:\localRepository</localRepository> setting.xml <?xml version"1.0" encoding"UTF-8"?><!-- Licensed to the Apache Soft…...
编程题-连接两字母单词得到的最长回文串(中等)
题目: 给你一个字符串数组 words 。words 中每个元素都是一个包含 两个 小写英文字母的单词。 请你从 words 中选择一些元素并按 任意顺序 连接它们,并得到一个 尽可能长的回文串 。每个元素 至多 只能使用一次。 请你返回你能得到的最长回文串的 长度…...
react 新手入门指南,常用命令
React 是一个用于构建用户界面的 JavaScript 库,它通过组件化的方式构建应用程序的 UI,适用于构建单页应用(SPA)。以下是一个详细的 React 新手入门指南,包括常用命令和基本概念。 1. 环境准备 在开始之前,确保你已经安装了 Node.js 和 npm。可以通过以下命令检查版本:…...
论文笔记(七十二)Reward Centering(三)
Reward Centering(三) 文章概括摘要3 基于值的奖励中心化4 案例研究: 以奖励为中心的 Q-learning5 讨论、局限性与未来工作致谢 文章概括 引用: article{naik2024reward,title{Reward Centering},author{Naik, Abhishek and Wan…...
【论文笔记-ECCV 2024】AnyControl:使用文本到图像生成的多功能控件创建您的艺术作品
AnyControl:使用文本到图像生成的多功能控件创建您的艺术作品 图1 AnyControl的多控制图像合成。该研究的模型支持多个控制信号的自由组合,并生成与每个输入对齐的和谐结果。输入到模型中的输入控制信号以组合图像显示,以实现更好的可视化。 …...
Vue3核心编译库@vuecompiler-core内容分享
vue/compiler-core 是 Vue 3 中的一个核心编译库,主要用于编译 Vue 的模板。它为 Vue 3 提供了处理模板编译的功能,包含了将模板转换为抽象语法树(AST)、生成渲染函数以及与响应式系统进行集成等功能。 vue/compiler-core 的主要…...
Redisson使用场景及原理
目录 一、前言 二、安装Redis 1、Windows安装Redis 2、启动方式 3、设置密码 三、项目集成Redission客户端 1、引入依赖 四、实用场景 1、操作缓存 2、分布式锁 3、限流 3.1 创建限流器 3.2 设置限流参数 3.3 获取令牌 3.4 带超时时间获取令牌 3.5 总结 一、…...
【二分查找 图论】P8794 [蓝桥杯 2022 国 A] 环境治理|普及
本文涉及的基础知识点 本博文代码打包下载 C二分查找 C图论 [蓝桥杯 2022 国 A] 环境治理 题目描述 LQ 国拥有 n n n 个城市,从 0 0 0 到 n − 1 n - 1 n−1 编号,这 n n n 个城市两两之间都有且仅有一条双向道路连接,这意味着任意两…...
c/c++蓝桥杯经典编程题100道(22)最短路径问题
最短路径问题 ->返回c/c蓝桥杯经典编程题100道-目录 目录 最短路径问题 一、题型解释 二、例题问题描述 三、C语言实现 解法1:Dijkstra算法(正权图,难度★★) 解法2:Bellman-Ford算法(含负权边&a…...
25中医研究生复试面试问题汇总 中医专业知识问题很全! 中医试全流程攻略 中医考研复试调剂真题汇总
各位备考中医研究生的小伙伴们,一想到复试,是不是立刻紧张到不行,担心老师会抛出一大堆刁钻的问题?别怕!其实中医复试也是有套路可循的,只要看完这篇攻略,你就会发现复试并没有想象中那么难&…...
stm32hal库寻迹+蓝牙智能车(STM32F103C8T6)
简介: 这个小车的芯片是STM32F103C8T6,其他的芯片也可以照猫画虎,基本配置差不多,要注意的就是,管脚复用,管脚的特殊功能,(这点不用担心,hal库每个管脚的功能都会给你罗列,很方便的.)由于我做的比较简单,只是用到了几个简单外设.主要是由带霍尔编码器电机的车模,电机…...
centos22.04 dpkg -l 输出状态标识含义
dpkg -l 输出状态标识含义 dpkg -l 命令用于列出系统中已安装的软件包,每行输出的前两个字符是软件包状态的标识,不同的组合代表不同的状态,具体含义如下: 第一个字符:表示期望的状态(Desired state) u:未知(Unknown)i:安装(Install)r:移除(Remove)p:清除(Pu…...
前端TypeScript 面试题及参考答案
目录 解释 unknown 与 any 的区别,如何安全使用 unknown 类型? 如何用类型守卫处理联合类型变量的方法调用? 实现一个工具类型 Nullable ,使 T 可被赋值为 null/undefined 如何用 keyof 和 in 关键字实现枚举类型到联合类型的转换? 类型断言 as 与尖括号语法有何差异…...
基于 Vue.js 和 Element UI 实现九宫格按钮拖拽排序功能 | 详细教程与代码实现
在Vue.js项目中使用vue-element-template(基于Element UI)实现按钮的九宫格拖拽排序功能,可以通过以下步骤实现。我们将使用vuedraggable库来实现拖拽排序功能。 1. 安装依赖 首先,确保你已经安装了vuedraggable库: …...
Spring Framework测试工具MockMvc介绍
目录 一、基本概念 二、主要特点 三、使用场景 四、工作原理 五、示例代码 接口创建 测试类创建 六、注解解释 AutoConfigureMockMvc WebMvcTest 一、基本概念 MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用…...
nginx 正向代理与反向代理
1. 正向代理(Forward Proxy) 正向代理是指 代理客户端 访问目标服务器,通常用于访问受限资源或隐藏客户端 IP。 工作原理 客户端请求代理服务器(如 nginx)。代理服务器代表客户端向目标网站发起请求。目标网站返回内…...
VUE 获取视频时长,无需修改数据库,前提当前查看视频可以得到时长
第一字段处 <el-table-column label"视频时长" align"center"> <template slot-scope"scope"> <span>{{ formatDuration(scope.row.duration) }}</span> </template> </el-ta…...
使用Jenkins实现Windows服务器下C#应用程序发布
背景 在现代化的软件开发流程中,持续集成和持续部署(CI/CD)已经成为不可或缺的一部分。 Jenkins作为一款开源的自动化运维工具,能够帮助我们实现这一目标。 本文将详细介绍如何在Windows服务器下使用Jenkins来自动化发布C#应用…...
