快速排序是计算机科学中最经典的排序算法之一,由 Tony Hoare 在 1960 年提出。它凭借平均时间复杂度 O (nlogn)、原地排序(空间复杂度 O (logn),主要来自递归栈)以及良好的实际性能,成为工业界处理大规模数据排序的首选算法之一。无论是在 LeetCode 算法题中,还是在考研计算机专业基础综合(408)的考试里,快速排序都是高频考点。

快速排序算法核心思路

快速排序的核心思想是分治法(Divide and Conquer),通过选择一个 “基准元素” 将数组分为两部分,使得左半部分的元素都小于等于基准,右半部分的元素都大于等于基准,然后递归地对两部分进行排序。

 关键步骤解析

(1)选择基准元素(Pivot)

基准元素的选择对快速排序的性能影响很大。常见的选择策略有:

  • 固定位置:如选择数组的第一个元素、最后一个元素或中间元素。
  • 随机选择:随机挑选数组中的一个元素作为基准,避免在有序数组上出现最坏情况。
  • 三数取中:选择数组第一个、中间和最后一个元素中的中位数作为基准,平衡性能。

本文以 “选择最后一个元素作为基准” 为例进行讲解,后续会在优化部分介绍其他策略。

(2)分区操作(Partition)

分区是快速排序的核心,目的是将数组划分为两部分,使得左部分元素≤基准,右部分元素≥基准。经典的 Lomuto 分区算法步骤如下:

  1. 设基准元素为pivot = arr[right](right为当前数组的右边界)。
  1. 初始化指针i,指向 “小于等于基准区域” 的边界(初始为left - 1)。
  1. 遍历数组从left到right - 1:
    • 若arr[j] ≤ pivot,则i右移一位,交换arr[i]与arr[j],将当前元素纳入 “小于等于基准区域”。
  1. 遍历结束后,交换arr[i + 1]与arr[right],此时基准元素被放置在正确位置(i + 1),左边元素均≤基准,右边元素均≥基准。
(3)递归排序

分区操作后,基准元素将数组分为左右两个子数组。递归地对左子数组([left, i])和右子数组([i + 2, right])执行快速排序,直到子数组长度为 0 或 1(此时数组已有序)。

 Java 实现(基础版)

public class QuickSortBasic {// 对外暴露的排序方法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]; // 选择最后一个元素作为基准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;}// 测试public static void main(String[] args) {int[] arr = {4, 7, 2, 5, 1, 3, 6};sort(arr);for (int num : arr) {System.out.print(num + " "); // 输出:1 2 3 4 5 6 7}}}

LeetCode 例题实战

例题 1:912. 排序数组(中等)

题目描述:给你一个整数数组 nums,请你将该数组升序排列。

示例

输入:nums = [5,2,3,1]

输出:[1,2,3,5]

解题思路:直接使用快速排序对数组进行排序。由于 LeetCode 的测试用例可能包含大量重复元素或有序数组,为避免最坏情况(时间复杂度退化至 O (n²)),可优化基准选择策略(如随机选择基准)。

Java 代码实现

class Solution {public int[] sortArray(int[] nums) {if (nums == null || nums.length <= 1) {return nums;}quickSort(nums, 0, nums.length - 1);return nums;}private void quickSort(int[] nums, int left, int right) {if (left < right) {int pivotIndex = randomPartition(nums, left, right); // 随机选择基准quickSort(nums, left, pivotIndex - 1);quickSort(nums, pivotIndex + 1, right);}}// 随机选择基准并分区private int randomPartition(int[] nums, int left, int right) {// 生成[left, right]范围内的随机索引int randomIndex = left + (int) (Math.random() * (right - left + 1));swap(nums, randomIndex, right); // 将随机基准交换到末尾return partition(nums, left, right); // 执行分区}private int partition(int[] nums, int left, int right) {int pivot = nums[right];int i = left - 1;for (int j = left; j < right; j++) {if (nums[j] <= pivot) {i++;swap(nums, i, j);}}swap(nums, i + 1, right);return i + 1;}private void swap(int[] nums, int i, int j) {int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}}

复杂度分析

  • 时间复杂度:平均 O (nlogn),最坏 O (n²)(随机化后最坏情况概率极低)。
  • 空间复杂度:O (logn),来自递归栈。

例题 2:215. 数组中的第 K 个最大元素(中等)

题目描述:给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例

输入: [3,2,1,5,6,4] 和 k = 2

输出: 5

解题思路:利用快速排序的分区思想(快速选择算法)。每次分区后,基准元素的位置 pivotIndex 是其在有序数组中的最终位置。若 pivotIndex = n - k(n 为数组长度),则该元素即为第 k 个最大元素;若 pivotIndex < n - k,则在右子数组中继续查找;否则在左子数组中查找。

Java 代码实现

class Solution {public int findKthLargest(int[] nums, int k) {int n = nums.length;int targetIndex = n - k; // 第k大元素在有序数组中的索引return quickSelect(nums, 0, n - 1, targetIndex);}private int quickSelect(int[] nums, int left, int right, int targetIndex) {if (left == right) {return nums[left]; // 子数组长度为1时直接返回}int pivotIndex = randomPartition(nums, left, right);if (pivotIndex == targetIndex) {return nums[pivotIndex]; // 找到目标元素} else if (pivotIndex < targetIndex) {return quickSelect(nums, pivotIndex + 1, right, targetIndex); // 右子数组查找} else {return quickSelect(nums, left, pivotIndex - 1, targetIndex); // 左子数组查找}}// 随机分区(同例题1)private int randomPartition(int[] nums, int left, int right) {int randomIndex = left + (int) (Math.random() * (right - left + 1));swap(nums, randomIndex, right);return partition(nums, left, right);}private int partition(int[] nums, int left, int right) {int pivot = nums[right];int i = left - 1;for (int j = left; j < right; j++) {if (nums[j] <= pivot) {i++;swap(nums, i, j);}}swap(nums, i + 1, right);return i + 1;}private void swap(int[] nums, int i, int j) {int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}}

复杂度分析

  • 时间复杂度:平均 O (n),最坏 O (n²)(随机化后概率极低)。
  • 空间复杂度:O (logn),来自递归栈。

考研 408 例题解析

例题 1:基本概念与时间复杂度分析(选择题)

题目:下列关于快速排序的叙述中,正确的是( )。

A. 快速排序的时间复杂度为 O (nlogn),空间复杂度为 O (n)

B. 快速排序是稳定的排序算法

C. 快速排序在最坏情况下的时间复杂度为 O (n²),此时数组已基本有序

D. 快速排序的分区操作可以通过一次线性扫描完成

答案:C、D

解析

  • A 错误:快速排序空间复杂度为 O (logn)(递归栈),而非 O (n)。
  • B 错误:快速排序不稳定(分区交换可能改变相等元素的相对顺序)。
  • C 正确:当数组有序或逆序时,每次分区只能将数组分为 1 和 n-1 两部分,时间复杂度退化至 O (n²)。
  • D 正确:经典分区算法通过一次线性扫描(遍历数组)即可完成。

例题 2:算法设计题(408 高频考点)

题目:已知一个整数数组A[0..n-1],设计一个算法,将所有负数移到正数之前(0 视为正数),要求不改变负数之间的相对顺序和正数之间的相对顺序,且时间复杂度为 O (n),空间复杂度为 O (1)。

解题思路:本题虽不直接考查排序,但可借鉴快速排序的分区思想。与快速排序不同的是,本题要求保持相对顺序(稳定),但题目限制空间复杂度为 O (1),因此不能使用额外数组。我们可以通过类似 “冒泡” 的方式,将负数逐步交换到前面,但时间复杂度会变为 O (n²)。不过,考研中更可能的考点是理解分区思想的变形应用。

优化思路:若允许空间复杂度为 O (n),可使用双指针 + 辅助数组:

  1. 遍历原数组,将负数依次放入辅助数组前半部分,正数放入后半部分。
  1. 将辅助数组复制回原数组。

但根据题目限制(空间 O (1)),这里提供基于分区思想的解法(注意:该方法可能改变相对顺序,仅作思路参考):

public class PartitionNegatives {public static void partitionNegatives(int[] A) {if (A == null || A.length <= 1) {return;}int i = -1; // 负数区域边界for (int j = 0; j < A.length; j++) {if (A[j] < 0) { // 遇到负数i++;swap(A, i, j); // 交换到负数区域}}}private static void swap(int[] A, int i, int j) {int temp = A[i];A[i] = A[j];A[j] = temp;}public static void main(String[] args) {int[] A = {1, -2, 3, -4, 5, -6};partitionNegatives(A);for (int num : A) {System.out.print(num + " "); // 输出:-2 -4 -6 1 3 5(相对顺序改变)}}}

考研答题要点

  1. 说明快速排序分区思想的核心:通过一次遍历划分区域。
  1. 指出本题限制(保持相对顺序)与快速排序的差异。
  1. 给出符合时间和空间复杂度的解法(如辅助数组法,并说明其稳定性)。

例题 3:快速排序与其他排序算法对比(综合题)

题目:比较快速排序、归并排序和堆排序的优缺点,并说明在什么情况下选择快速排序更合适。

答案要点

  • 快速排序
    • 优点:平均时间复杂度 O (nlogn),原地排序(空间 O (logn)),实际性能优异。
    • 缺点:不稳定,最坏时间复杂度 O (n²),对有序数组敏感。
    • 适用场景:数据量大、分布随机、

希望本文能够帮助读者更深入地理解快速排序,并在实际项目中发挥其优势。谢谢阅读!


希望这份博客能够帮助到你。如果有其他需要修改或添加的地方,请随时告诉我。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/90903.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/90903.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/90903.shtml

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

相关文章

unity 有打击感的图片,怎么做动画,可以表现出良好的打击效果

完整实现脚本:using UnityEngine; using UnityEngine.UI; using System.Collections;[RequireComponent(typeof(Image))] public class HitEffectController : MonoBehaviour {[Header("基础设置")]public float hitDuration 0.5f; // 打击效果总时长[Header("…

cuda编程笔记(7)--多GPU上的CUDA

零拷贝内存 在流中&#xff0c;我们介绍了cudaHostAlloc这个函数&#xff0c;它有一些标志&#xff0c;其中cudaHostAllocMapped允许内存映射到设备&#xff0c;也即GPU可以直接访问主机上的内存&#xff0c;不用额外再给设备指针分配内存 通过下面的操作&#xff0c;即可让设…

IP地址混乱?监控易IPAM实现全网地址自动化管理与非法接入告警

IP地址出现混乱状况&#xff1f;监控易IPAM能够达成对全网地址予以自动化管理的目标&#xff0c;同时还可针对非法接入的情况发出告警信息。办公室毫无预兆地突然断网了&#xff0c;经过一番仔细排查之后&#xff0c;发现原来是IP地址出现了冲突的情况。有人私自接了路由器&…

安全监测预警平台的应用场景

随着城市化进程加快和基础设施规模扩大&#xff0c;各类安全风险日益突出。安全监测预警平台作为现代安全管理的重要工具&#xff0c;通过整合物联网、大数据、人工智能等先进技术&#xff0c;实现对各类安全隐患的实时监测、智能分析和精准预警。本文将详细探讨安全监测预警平…

007_用例与应用场景

用例与应用场景 目录 内容创作编程开发数据分析客户服务教育培训商业智能研究辅助 内容创作 文案撰写 应用场景&#xff1a; 营销文案和广告语产品描述和说明书社交媒体内容邮件营销内容 实际案例&#xff1a; 任务&#xff1a;为新款智能手表撰写产品描述 输入&#x…

Unity物理系统由浅入深第一节:Unity 物理系统基础与应用

Unity物理系统由浅入深第一节&#xff1a;Unity 物理系统基础与应用 Unity物理系统由浅入深第二节&#xff1a;物理系统高级特性与优化 Unity物理系统由浅入深第三节&#xff1a;物理引擎底层原理剖析 Unity物理系统由浅入深第四节&#xff1a;物理约束求解与稳定性 Unity 引擎…

《[系统底层攻坚] 张冬〈大话存储终极版〉精读计划启动——存储架构原理深度拆解之旅》-系统性学习笔记(适合小白与IT工作人员)

&#x1f525; 致所有存储技术探索者笔者近期将系统攻克存储领域经典巨作——张冬老师编著的《大话存储终极版》。这部近千页的存储系统圣经&#xff0c;以庖丁解牛的方式剖析了&#xff1a;存储硬件底层架构、分布式存储核心算法、超融合系统设计哲学等等。喜欢研究数据存储或…

flutter鸿蒙版 环境配置

flutter支持开发鸿蒙,但是需要专门的flutter鸿蒙项目, Flutter鸿蒙化环境配置&#xff08;windows&#xff09;_flutter config --ohos-sdk-CSDN博客

Java 高级特性实战:反射与动态代理在 spring 中的核心应用

在 Java 开发中&#xff0c;反射和动态代理常被视为 “高级特性”&#xff0c;它们看似抽象&#xff0c;却支撑着 Spring、MyBatis 等主流框架的核心功能。本文结合手写 spring 框架的实践&#xff0c;从 “原理” 到 “落地”&#xff0c;详解这两个特性如何解决实际问题&…

Codeforces Round 855 (Div. 3)

A. Is It a Cat? 去重&#xff0c; 把所有字符看成大写字符&#xff0c; 然后去重&#xff0c; 观察最后结果是不是“MEOW” #include <bits/stdc.h> #define int long longvoid solve() {int n;std::cin >> n;std::string ans, t;std::cin >> ans;for (int…

Scrapy选择器深度指南:CSS与XPath实战技巧

引言&#xff1a;选择器在爬虫中的核心地位在现代爬虫开发中&#xff0c;​​选择器​​是数据提取的灵魂工具。根据2023年网络爬虫开发者调查数据显示&#xff1a;​​92%​​ 的数据提取错误源于选择器编写不当熟练使用选择器的开发效率相比新手提升 ​​300%​​同时掌握CSS…

Windos服务器升级MySQL版本

Windos服务器升级MySQL版本 1.备份数据库 windows下必须以管理员身份运行命令行工具进行备份&#xff0c;如果没有配置MySQL的环境变量&#xff0c;需要进入MySQL Server 的bin目录输入指令&#xff0c; mysqldump -u root -p --all-databases > backup.sql再输入数据库密码…

告别频繁登录!Nuxt3 + TypeScript + Vue3实战:双Token无感刷新方案全解析

前言 在现代 Web 应用中&#xff0c;身份认证是保障系统安全的重要环节。传统的单 Token 认证方式存在诸多不足&#xff0c;如 Token 过期后需要用户重新登录&#xff0c;影响用户体验。本文将详细介绍如何在 Nuxt3 TypeScript Vue3 项目中实现无感刷新 Token 机制&#xff…

Linux——Redis

目录 一、Redis概念 1.1 Redis定义 1.2 Redis的特点 1.3 Redis的用途 1.4 Redis与其他数据库的对比 二、Redis数据库 三、Redis五个基本类型 3.1 字符串 3.2 列表(list) ——可以有相同的值 3.3 集合(set) ——值不能重复 3.4 哈希(hash) ——类似于Map集合 3.5 有序…

【AI大模型】部署优化量化:INT8压缩模型

INT8&#xff08;8位整数&#xff09;量化是AI大模型部署中最激进的压缩技术&#xff0c;通过将模型权重和激活值从FP32降至INT8&#xff08;-128&#xff5e;127整数&#xff09;&#xff0c;实现4倍内存压缩2-4倍推理加速&#xff0c;是边缘计算和高并发服务的核心优化手段。…

LFU 缓存

题目链接 LFU 缓存 题目描述 注意点 1 < capacity < 10^40 < key < 10^50 < value < 10^9对缓存中的键执行 get 或 put 操作&#xff0c;使用计数器的值将会递增当缓存达到其容量 capacity 时&#xff0c;则应该在插入新项之前&#xff0c;移除最不经常使…

检查输入有效性(指针是否为NULL)和检查字符串长度是否为0

检查输入有效性&#xff08;指针是否为NULL&#xff09;和检查字符串长度是否为0 这两个检查针对的是完全不同的边界情况&#xff0c;都是必要的防御性编程措施&#xff1a; 1. 空指针检查 if(!src) 目的&#xff1a;防止解引用空指针场景&#xff1a;当调用者传入 NULL 时风险…

Apache POI 的 HSSFWorkbook、SXSSFWorkbook和XSSFWorkbook三者的区别

HSSFWorkbook 专用于处理Excel 97-2003&#xff08;.xls&#xff09;格式的二进制文件。基于纯Java实现&#xff0c;所有数据存储在内存中&#xff0c;适合小规模数据&#xff08;通常不超过万行&#xff09;。内存占用较高&#xff0c;但功能完整&#xff0c;支持所有旧版Exce…

冷冻电镜重构的GPU加速破局:从Relion到CryoSPARC的并行重构算法

点击 “AladdinEdu&#xff0c;同学们用得起的【H卡】算力平台”&#xff0c;H卡级别算力&#xff0c;按量计费&#xff0c;灵活弹性&#xff0c;顶级配置&#xff0c;学生专属优惠。 一、冷冻电镜重构的算力困局 随着单粒子冷冻电镜&#xff08;cryo-EM&#xff09;分辨率突破…

算法学习笔记:16.哈希算法 ——从原理到实战,涵盖 LeetCode 与考研 408 例题

在计算机科学中&#xff0c;哈希算法&#xff08;Hash Algorithm&#xff09;是一种将任意长度的输入数据映射到固定长度输出的技术&#xff0c;其输出称为哈希值&#xff08;Hash Value&#xff09;或散列值。哈希算法凭借高效的查找、插入和删除性能&#xff0c;在数据存储、…