快速排序算法详解

算法原理

快速排序是一种分治的策略的排序算法。它的核心排序思想是将问题不断的分解为子问题。以数组为例进行介绍更容易理解,创建一个数组或者vector,假设是std::vector<int> a{3,2, 1, 5, 4,7},要对a从小到大进行排序:

  1. 首先在a中任意选择一个数值(为了简单起见,这个中间值每次都取数组的第一个数值即可),以这个数值为中间值,通过遍历的方式分别找出小于这个中间值的所有数字放到一个数组或者vector中,这里将包含所有小于中间值数字的数组成为left;同时找出所有大于这个中间值的数字放到一个数组或者vector中,这里将包含所有大于中间值数字的数组成为right。
  2. 将数组left作为一个新的整体,重复1的操作,直到数组的长度小于等于1截止,停止重复。
  3. 第2步完成后,整个left子数组已经是一个有序的数组
  4. 将数组right作为一个新的整体,重复1的操作,直到数组的长度小于等于1截止,停止重复。
  5. 第4步完成后,整个right子数组已经一个有序的数组
  6. 拼接数组,left数组+中间值+right数组

上面的过程有一个隐性的排序,即在步骤1中每次都会都数组根据中间值编译找出所有小于这个中间值的数字和大于中间值的数组,然后再对子数组重复进行同样的操作,直到最后的子数组长度为1的时候,就完成了这个排序,可以这样理解,在每次问题分解过程中都会遍历当前数组根据选择的数值作为临界值进行一次“排序”,这里的“排序”并不是对整个数组进行排序,而是以选择的临界值分别找出小于这个临界值和大于这个临界值的数字,虽然小于或大于这个临界值的部分没有进行排序,但是我们我们可以将他们继续分解,继续在他们中找出一个临界值,重复这个过程,直至数组的大小为1。这样整个数组通过这样的方式实现了排序的需求。

把上面的内容进行归纳总结:

快速排序是一种分治策略(Divide and Conquer)的排序算法:

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

代码实例

c++实现

//快速排序算法,输入是一个数组,输出也是一个数组
//函数设计,参数为一个vector,无返回值,参数既是输出也是输出,第二个参数表示这个vector的索引
//第三个参数是vector的右索引
/*
 *测试用例:
 *1.只有一个元素
 *2.包含2个元素
 *3.包含多个元素,元素按从小到大顺序排列
 *4.包含多个元素元素从大小顺序排列
 *5.包含多个元素,顺序无序
 *6.包含多个元素,元素都相同
 *
 *
 */
void MyQsort(std::vector<int>& vector)
{
    //如果只有一个元素,无需进行排序,这是基线条件
    if(vector.size() <=1 )
        return;

    int value = vector[0];
    std::vector<int> leftVec;  //用于存储左侧比value小的数值
    std::vector<int> rightVec;  //用于存储右侧比value大的数值
    for(int i = 1; i < vector.size(); i++)
    {
        if(vector[i] >= value)
            rightVec.push_back(vector[i]);
        else
            leftVec.push_back(vector[i]);
    }

    MyQsort(leftVec);  //比该数值小的进行快速排序,成为分解为子问题
    MyQsort(rightVec);  //比该数值大的进行快速排序,成为分解为子问题

    vector.clear();
    vector.insert(vector.begin(), leftVec.begin(), leftVec.end());
    vector.push_back(value);
    vector.insert(vector.end(), rightVec.begin(), rightVec.end());
    return;
}

 python实现

import os

def my_qsort(lst):
  length = len(lst)
  if length <= 1:
    return lst
  value = lst[0]
  leftLst = [x for x in lst[1:] if x < value]
  rightLst = [x for x in lst[1:] if x >= value]

  leftLst = my_qsort(leftLst)
  rightLst = my_qsort(rightLst)
  lst = leftLst+[value]+rightLst
  print(f'--lst={lst}')
  return lst

lst = [5,2,3,8,1,7]
lst = my_qsort(lst)
print(f'lst={lst}')

#输出
--lst=[1, 2, 3]
--lst=[7, 8]
--lst=[1, 2, 3, 5, 7, 8]
lst=[1, 2, 3, 5, 7, 8]

时间复杂度分析

快速排序的时间复杂度分析,利用二叉树来分析快速排序的时间复杂度更容易理解。

结合前面的算法实现,可以发现如下的规律:

  1. 假设待排序的数组中有个n个数值,递归的第一层作为二叉树的根节点,再这一层中有一个for循环遍历了n次,所以设这个节点的值为n;同时递归的首层分出了两个子数组,将这两个子树作为根节点的两个子节点,并且这两个字子节点的和是n
  2. 两个子节点每个都做一个新的二叉树的根节点,然后继续生成的新的子节点,但是有一个规律即两个子节点的和都等于根节点的值。
  3. 由此可以得出一个结论二叉树每一层的和都是n
  4. 要求这棵二叉树中所有节点值的和就是时间复杂度

这个问题就转变成了一棵二叉树问题,现在求解二叉树的最好时间复杂度变成了如何求解二叉树如何分布时间复杂度最低;求解最差时间复杂度变成了求解二叉树如何分布时间复杂度最高。

最佳时间复杂度

最佳的时间复杂度问题变成了,每次分区都将数组分成平均相等的两份,请看下面的图片:

由图可知二叉树的每一层的节点的和为n,那么要求所有二叉树节点的和需要知道树的高度:

树的高度:h = logn

因为每一层都是上一层数值的1/2。

现在知道了树的高度,那么最佳的时间复杂度为:

最佳时间复杂度 = h * n=O(nlogn) 

最差时间复杂度

最差的时间复杂度就需要求解二叉树如何分布,可以使得所有节点的和最大。那只有数组分区时极度不平衡,那么每次数据在一边。如下图所示:

这是时候变成了:最差时间复杂度 = n + (n-1) + (n-2) + ...+1=(n+1)*n/2=O(n^{2})

空间复杂度分析

最佳空间复杂度

递归函数的空间复杂度一般将函数栈作为一个空间单位,因为一个函数栈包含了临时变量、参数等。占用函数栈个数最少的二叉树分布就是最佳空间复杂度。那么怎么理解空间复杂度最低呢?答案是使的二叉树的高度最低,那么数组每次如何分布才能最低呢?那只有二叉平衡树高度最低了。即每次数组分区按照平均分配的方法进行分区。

最佳空间复杂度 = 最短的树的高度 = O(logn)

最差时间复杂度

同理,最差的时间复杂度肯定递归调用同时占据最多个函数栈空间的就是最差空间复杂度。

最差空间复杂度 = 最大的二叉树高度 = O(n)

平均时间/空间复杂度

通过前面的章节知道了存在最佳时间复杂度和最差时间复杂度,但是有另外一个概念叫做平均时间复杂度,那这个平均时间复杂度应该如何评估呢?我从一本书里得到了这样的答案:

平均时间复杂度 = 最佳时间复杂度

最佳情况就是平均情况,以快速排序算法为例,只要你随机的选择一个数值为基准值进行排序,那么得到的时间就是最佳时间复杂度。

平均空间复杂度同理:平均空间复杂度 = 最佳空间复杂度

快速排序算法是最快的排序算法之一。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/982088.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux-网络】HTTP的清风与HTTPS的密语

&#x1f3ac; 个人主页&#xff1a;谁在夜里看海. &#x1f4d6; 个人专栏&#xff1a;《C系列》《Linux系列》《算法系列》 ⛰️ 道阻且长&#xff0c;行则将至 目录 &#x1f4da; 引言 &#x1f4da; 一、HTTP &#x1f4d6; 1.概述 &#x1f4d6; 2.URL &#x1f5…

2025-03-05 学习记录--C/C++-PTA 习题5-6 使用函数输出水仙花数

合抱之木&#xff0c;生于毫末&#xff1b;九层之台&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、题目描述 ⭐️ 二、代码&#xff08;C语言&#xff09;⭐️ #include <stdio.h>int narcissistic( int number ); vo…

Vue的简单入门 三

目录 侦听器 watch 注意 表单输入绑定 v-model v-model修饰符​编辑 lazy number Trim 模板引用 组件组成 组件引用三步走 组件的嵌套关系 header Main Aside Aritice Item App.vue组件引入三个子组件 组件的注册方式 全局注册组件的方法 (1) Vue 2 语…

跨域-告别CORS烦恼

跨域-告别CORS烦恼 文章目录 跨域-告别CORS烦恼[toc]1-参考网址2-思路整理1-核心问题2-个人思考3-脑洞打开4-个人思考-修正版1-个人思考2-脑洞打开 3-知识整理1-什么是跨域一、同源策略简介什么是源什么是同源是否是同源的判断哪些操作不受同源策略限制跨域如何跨域 二、CORS 简…

大模型核心要素完全解析:从数字神经元到智能对话的奥秘

一、神经网络的基石&#xff1a;模型参数 1.1 参数的本质解密 大模型参数是指在大规模机器学习模型&#xff0c;特别是像大型语言模型&#xff08;LLM&#xff09;等中&#xff0c;用于描述模型结构和功能的各种变量和数据。 其中大模型参数又分为权重参数和偏置参数&#x…

Android ChatOn-v1.66.536-598-[构建于ChatGPT和GPT-4o之上]

ChatOn 链接&#xff1a;https://pan.xunlei.com/s/VOKYnq-i3C83CK-HJ1gfLf4gA1?pwdwzwc# 添加了最大无限积分 删除了所有调试信息 语言&#xff1a;全语言支持

前端开发10大框架深度解析

摘要 在现代前端开发中&#xff0c;框架的选择对项目的成功至关重要。本文旨在为开发者提供一份全面的前端框架指南&#xff0c;涵盖 React、Vue.js、Angular、Svelte、Ember.js、Preact、Backbone.js、Next.js、Nuxt.js 和 Gatsby。我们将从 简介、优缺点、适用场景 以及 实际…

NL2SQL-基于Dify+阿里通义千问大模型,实现自然语音自动生产SQL语句

本文基于Dify阿里通义千问大模型&#xff0c;实现自然语音自动生产SQL语句功能&#xff0c;话不多说直接上效果图 我们可以试着问他几个问题 查询每个部门的员工数量SELECT d.dept_name, COUNT(e.emp_no) AS employee_count FROM employees e JOIN dept_emp de ON e.emp_no d…

2025年渗透测试面试题总结-字某跳动-渗透测试实习生(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 字某跳动-渗透测试实习生 渗透流程信息收集如何处理子域名爆破中的泛解析问题绕过CDN寻找真实IPPHPINFO页面关注…

从文件到块: 提高 Hugging Face 存储效率

Hugging Face 在Git LFS 仓库中存储了超过30 PB 的模型、数据集和 Spaces。由于 Git 在文件级别进行存储和版本控制&#xff0c;任何文件的修改都需要重新上传整个文件。这在 Hub 上会产生高昂的成本&#xff0c;因为平均每个 Parquet 和 CSV 文件大小在 200-300 MB 之间&#…

大型语言模型演变之路:从Transformer到DeepSeek-R1

大型语言模型的崛起被认为是人工智能领域的一次革命&#xff0c;从2017年Transformer架构的引入开始&#xff0c;到2025年DeepSeek-R1的推出&#xff0c;每一步都在不断改变着人机交互的方式&#xff0c;推动着学术界与产业界的深度融合。 1. Transformer的引领&#xff08;201…

设计模式(7)——SOLID原则之接口隔离原则

设计模式&#xff08;7&#xff09;——SOLID原则之接口隔离原则 概念示例总结 概念 客户端不应被强迫依赖于其不使用的方法。这句话的意思是指尽量缩小接口的范围&#xff0c;使得客户端的类不必实现其不需要的行为。 根据接口隔离原则&#xff0c;你必须将“臃肿”的方法拆…

Excel的行高、列宽单位不统一?还是LaTeX靠谱

想要生成田字格、米字格、带拼音标准&#xff0c;方便小学生书法和练字。Word&#xff0c;Excel之类所见即所得是最容易相当的方式。但它们处理带田字格之类背景时&#xff0c;如果没有专用模板、奇奇怪怪的插件&#xff0c;使用起来会碰到各种问题。比如&#xff0c;Word里面用…

C++学习之路,从0到精通的征途:入门基础

目录 一.C的第一个程序 二.命名空间 1.namespace的价值 2.命名空间的定义 3.命名空间使用 三.C的输入与输出 1.<iostream> 2.流 3.std(standard) 四.缺省参数 1.缺省参数的定义 2.全缺省/半缺省 3.声明与定义 ​五.函数重载 1.参数个数不同 2.参数类型不…

rust学习笔记12-hashmap与1. 两数之和

rust集合中也有hashmap&#xff0c;昨天已经提到过&#xff0c;学过java同学再熟悉不过了&#xff0c;一道经典面试题问hashmap在java1.8的实现原理&#xff0c;数组哈希表红黑树&#xff0c;rust中hashmap在功能上和java一样&#xff0c;但实现上有很大差别&#xff0c;它的基…

通过多线程同时获取H264和H265码流

目录 一.RV1126 VI采集摄像头数据并同时编码H264、H265的大概流程​编辑​编辑 1.1初始化VI模块&#xff1a; 1.2H264、H265的VENC模块初始化&#xff1a; 1.3VI分别绑定H264的VENC层和H265的VENC层&#xff1a; ​​​​​​​1.4开启H264线程采集H264的VENC数据&#xff…

SpringBoot为什么要禁止循环依赖?

大家好&#xff0c;我是锋哥。今天分享关于【SpringBoot为什么要禁止循环依赖?】面试题。希望对大家有帮助&#xff1b; SpringBoot为什么要禁止循环依赖? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring Boot 和 Spring 框架之所以要避免循环依赖&#xf…

The Rust Programming Language 学习 (三)

所有权 所有权&#xff08;系统&#xff09;是 Rust 最为与众不同的特性&#xff0c;它让 Rust 无需垃圾回收器&#xff08;garbage collector&#xff09;即可保证内存安全。因此&#xff0c;理解 Rust 中所有权的运作方式非常重要。 这里是非常重非常重的一个知识点,这里一…

基于物联网技术的电动车防盗系统设计(论文+源码)

1总体设计 本课题为基于物联网技术的电动车防盗系统&#xff0c;在此将整个系统架构设计如图2.1所示&#xff0c;其采用STM32F103单片机为控制器&#xff0c;通过NEO-6M实现GPS定位功能&#xff0c;通过红外传感器检测电瓶是否离开位&#xff0c;通过Air202 NBIOT模块将当前的数…

雷池WAF的为什么选择基于Docker

Docker 是一种开源的容器化平台&#xff0c;可以帮助开发人员将应用程序及其所有依赖项打包到一个称为容器的独立、可移植的环境中。Docker 的核心概念包括以下几点&#xff1a; 容器&#xff1a;Docker 使用容器来封装应用程序及其依赖项&#xff0c;使其能够在任何环境中都能…