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

排序---快速排序(Quick Sort)

一、算法核心概念

快速排序是一种分治法(Divide and Conquer) 思想的排序算法,由计算机科学家Tony Hoare于1960年提出。其核心思想是:通过选择一个“基准元素”(Pivot),将数组划分为两个子数组,其中左子数组的元素均小于等于基准,右子数组的元素均大于等于基准;然后递归地对两个子数组执行相同操作,最终使整个数组有序。

快速排序的高效性源于分区操作(Partition)——每次分区能将一个元素(基准)放到最终位置,同时将问题规模缩小一半(理想情况),从而实现接近O(nlogn)的时间复杂度。

二、基本工作原理

快速排序的工作流程可概括为三步:选择基准→分区→递归排序,具体如下:

  1. 选择基准(Pivot Selection)
    从数组中选择一个元素作为基准(如第一个、最后一个、中间元素或随机元素)。基准的选择直接影响算法效率(后续优化部分详细说明)。

  2. 分区(Partition)
    重新排列数组,使所有小于基准的元素移到基准左侧,所有大于基准的元素移到基准右侧(等于基准的元素可放任意侧)。分区后,基准元素的位置即为其在最终有序数组中的位置。

  3. 递归排序
    对基准左侧的子数组和右侧的子数组分别重复上述步骤(选择基准→分区),直至子数组长度为0或1(天然有序)。

在这里插入图片描述

三、分区策略(核心步骤详解)

分区是快速排序的核心操作,常用的分区方法有双边循环法单边循环法,以下以双边循环法为例详细说明:

双边循环法步骤(升序排序):
  • 初始状态:设数组为arr,左边界left,右边界right,选择arr[left]为基准pivot
  • 左指针ileft开始向右移动,寻找第一个大于pivot的元素;
  • 右指针jright开始向左移动,寻找第一个小于pivot的元素;
  • i < j,交换arr[i]arr[j],重复上述步骤;
  • i >= j时,交换arr[left](基准)和arr[j],此时j为基准的最终位置,分区完成。
四、实例演示(完整排序过程)

以数组[4, 7, 6, 5, 3, 2, 8, 1]为例,演示快速排序过程:

  1. 第一次分区

    • 选择基准pivot = 4(第一个元素),left=0right=7
    • 左指针i右移,找到第一个大于4的元素7(索引1);
    • 右指针j左移,找到第一个小于4的元素1(索引7);
    • 交换71→数组变为[4, 1, 6, 5, 3, 2, 8, 7]
    • 继续移动指针:i找到6(索引2),j找到2(索引5),交换→[4, 1, 2, 5, 3, 6, 8, 7]
    • 继续移动:i找到5(索引3),j找到3(索引4),交换→[4, 1, 2, 3, 5, 6, 8, 7]
    • 此时i=4j=4i >= j),交换基准4arr[j][3, 1, 2, 4, 5, 6, 8, 7]
    • 分区完成,基准4的位置为索引3,左子数组[3,1,2],右子数组[5,6,8,7]
  2. 递归处理左子数组[3,1,2]

    • 选择基准3,分区后得到[2,1,3],基准3在索引2,左子数组[2,1]
    • 递归处理[2,1],分区后得到[1,2]
  3. 递归处理右子数组[5,6,8,7]

    • 选择基准5,分区后5在索引0,右子数组[6,8,7]
    • 处理[6,8,7],选择基准6,分区后6在索引0,右子数组[8,7]
    • 处理[8,7],分区后得到[7,8]
  4. 最终有序数组[1, 2, 3, 4, 5, 6, 7, 8]

五、时间复杂度与空间复杂度
  • 时间复杂度

    • 理想情况(基准每次将数组分为等长两部分):每轮分区处理n个元素,递归深度为logn,总操作次数为O(nlogn);
    • 最坏情况(基准为数组的最值,如已排序数组选第一个元素):每次分区仅减少1个元素,递归深度为n,总操作次数为O(n²);
    • 平均情况:通过合理选择基准(如随机选择),可避免最坏情况,平均时间复杂度为O(nlogn)
  • 空间复杂度
    快速排序为原地排序(仅需常数空间存储临时变量),但递归调用会产生栈空间开销:

    • 理想情况:递归深度为logn,空间复杂度O(logn)
    • 最坏情况:递归深度为n,空间复杂度O(n)
六、稳定性分析

快速排序是不稳定的排序算法

  • 原因:分区过程中,相等元素的相对顺序可能被破坏。例如,数组[2, 2, 1]中,若选择第一个2为基准,分区时会将1与第一个2交换,导致两个2的相对顺序颠倒(变为[1, 2, 2],虽然结果有序,但原数组中第一个2在第二个2之前,排序后位置交换)。
七、优化策略(避免最坏情况)

快速排序的性能极大依赖基准选择,以下是常见优化手段:

  1. 随机选择基准
    每次从当前区间随机选择一个元素作为基准,避免在有序数组中总是选择最值作为基准(概率上降低最坏情况发生的可能性)。

  2. 三数取中法
    从区间的首、尾、中间三个位置选择中间值作为基准(如median(arr[left], arr[mid], arr[right])),适用于已知数据可能接近有序的场景。

  3. 小规模子数组改用插入排序
    当子数组长度小于阈值(如10)时,改用插入排序(插入排序在小规模数据上效率高于快速排序,减少递归开销)。

  4. 尾递归优化
    对较大的子数组采用递归,较小的子数组采用循环,减少递归栈深度(避免栈溢出)。

八、C++实现代码(含优化)

以下实现采用随机选择基准双边循环分区,并添加基本边界处理:

#include <iostream>
#include <vector>
#include <cstdlib>  // 用于rand()和srand()
#include <ctime>    // 用于time()初始化随机数种子
using namespace std;// 交换两个元素
void swap(int& a, int& b) {int temp = a;a = b;b = temp;
}// 随机选择基准并分区(双边循环法)
int partition(vector<int>& arr, int left, int right) {// 随机选择基准索引(避免最坏情况)int pivotIndex = left + rand() % (right - left + 1);// 将基准交换到区间左侧(方便分区逻辑)swap(arr[left], arr[pivotIndex]);int pivot = arr[left];  // 基准值int i = left;   // 左指针(从left开始)int j = right;  // 右指针(从right开始)while (i < j) {// 右指针左移:找到第一个小于pivot的元素while (i < j && arr[j] >= pivot) {j--;}// 左指针右移:找到第一个大于pivot的元素while (i < j && arr[i] <= pivot) {i++;}// 交换左右指针指向的元素if (i < j) {swap(arr[i], arr[j]);}}// 将基准放到最终位置(i=j处)swap(arr[left], arr[i]);return i;  // 返回基准索引
}// 快速排序主函数
void quickSort(vector<int>& arr, int left, int right) {// 递归终止条件:区间长度 <= 1if (left >= right) {return;}// 分区:得到基准的最终位置int pivotPos = partition(arr, left, right);// 递归排序左子数组quickSort(arr, left, pivotPos - 1);// 递归排序右子数组quickSort(arr, pivotPos + 1, right);
}int main() {// 初始化随机数种子(确保每次运行随机基准不同)srand(time(nullptr));vector<int> arr = {4, 7, 6, 5, 3, 2, 8, 1};cout << "排序前:";for (int num : arr) {cout << num << " ";}quickSort(arr, 0, arr.size() - 1);cout << "\n排序后:";for (int num : arr) {cout << num << " ";}// 输出:排序前:4 7 6 5 3 2 8 1 //      排序后:1 2 3 4 5 6 7 8 return 0;
}
九、适用场景与优缺点
适用场景:
  1. 大规模数据排序:平均时间复杂度O(nlogn),实际性能优于归并排序和堆排序(缓存友好,局部性好);
  2. 内存受限场景:原地排序(除递归栈外无需额外空间),空间效率高;
  3. 通用排序需求:C++标准库的std::sort、Java的Arrays.sort(针对基本类型)等均采用快速排序的变种(如 introsort)。
优点:
  • 平均性能优异,实际应用中排序速度快;
  • 原地排序,空间复杂度低(O(logn));
  • 对缓存友好(局部访问数据,符合CPU缓存机制)。
缺点:
  • 不稳定,不适合要求保持相等元素相对顺序的场景;
  • 最坏情况时间复杂度为O(n²)(需通过基准选择优化避免);
  • 递归实现可能导致栈溢出(可通过尾递归优化解决)。

快速排序是分治法的经典应用,通过“选择基准→分区→递归”三步实现高效排序。其核心优势在于平均O(nlogn)的时间复杂度和原地排序特性,使其成为实际应用中最常用的排序算法之一。尽管存在不稳定性和最坏情况风险,但通过随机基准、三数取中等优化策略,可有效规避缺陷。理解快速排序的分区逻辑和优化思想,不仅能掌握一种高效排序方法,更能深化对分治法的理解,为解决复杂算法问题提供思路。

http://www.dtcms.com/a/374822.html

相关文章:

  • 开源鸿蒙北向框架开发:系统服务理论详解
  • C/C++---动态内存管理(new delete)
  • Ubuntu系统安全合规配置
  • Chrome 核心事件循环揭秘:TaskSequenceManager 与 MessagePump 的设计与实现
  • Perforce QAC 2025.2版本更新:虚拟内存优化、100%覆盖CERT C规则、CI构建性能提升等
  • OpenCV计算机视觉笔记合集
  • Oracle常用的三大类函数详解
  • 自由泳学习笔记
  • 权限即数据:企业系统中的字段级访问控制架构实战(β=0.6)
  • 研学旅游产品设计实训室:赋能产品落地,培养实用人才
  • Android vs iOS 启动/内存/渲染 对照表
  • WAF如何应对金融领域的网络威胁和黑客攻击
  • YOLOv11改进大全:从卷积层到检测头,全方位提升目标检测性能
  • 机器学习04——决策树(信息增益、信息增益率、ID3、C4.5、CART、剪枝、连续值缺失值处理)
  • Javaweb - 14.6 - Vue3 数据交互 Axios
  • LeetCode 单调栈 739. 每日温度
  • Spark面试题及详细答案100道(71-80)-- 配置与部署
  • UDP特点及报文结构
  • ollama离线部署加载Qwen3-0.6b模型
  • 零基础12周精通Linux学习计划
  • Linux Shell 条件测试与 if 语句全解析
  • C语言内存精讲系列(九):深化详述 int 3(附录:int3 调试关键工具与实战案例)
  • 案例开发 - 日程管理 - 第六期
  • TCP 三次握手、四次挥手
  • 问题排查:之前运行正常的系统,突然批量接口报 404
  • 【Java实战㊱】Spring Boot邂逅Redis:缓存加速的奇妙之旅
  • Spring Cache 多租户缓存隔离解决方案实践
  • Mybatis-12 第三方缓存-EhCache
  • 【C++】特别的程序错误处理方式——异常机制
  • 嵌入式设备上mqtt库的使用