C++算法之快速排序

2023-10-27

C++算法之快速排序



一、快速排序引出

我们知道,给一个长度为n的序列排序,有三种很简单的算法:选择排序、冒泡排序、插入排序。这三种算法的复杂度均为O(n^2)

如果按照计算机1秒钟可以进行10^8次计算作为参照,那么它1秒之内可以排序的序列长度大概为10^4这个数量级。

然而,在实际生活中,10^4级别并不是一个很大的数字,比如说山东每年会有超过50万人参加高考。如果我们想将山东省内所有学生按照高考成绩排序的话,使用O(n^2)将会运行:
在这里插入图片描述
不得不说,这样的算法并不算高效。那么,我们能不能设计出更高效的算法呢?

  • 一种优化方法
    假设现在,我们只想对1~n的数字排序。并且现在我们有个排序算法,运行复杂度刚好是n^2

如果:

先把该序列分成两部分,一部分是1~(n/2),另一部分是(n/2+1)~n

然后对两个序列进行分别排序 ;

最后再将两部分贴在一起,这个算法的复杂度是多少呢?

1、首先,我们将序列分为长度相等的大小两部分。

此时我们需要将所有<=n/2的数字调出来放到一边;

将所有>n/2的数字挑出来放到另一边;

这个步骤相当于将所有数字看一遍,所以复杂度是O(n)

2、然后,我们使用原来掌握的排序算法,分别给分好的两个序列排序。

因为两个序列的长度都是n/2,所以两边排序的复杂度都是(n/2)2 = n2 / 4。两部分排序的总复杂度是(n2 / 4) * 2 = n2 / 2。

3、最后,我们需要把两部分的排序结果贴在一起。

这个操作几乎不费时间。

所以,我们最终复杂度将是n^2 / 2 + n。和原来直接运行排序算法得到复杂度为n^2 相/比,我们节省了近一半的时间!

由此,我们只需要将原序列划分一下,两边分别排序,最后将该序列合并,就能节省一半的时间(此时因为复杂度仍为平方级别,所以,我们只是在原算法的基础上优化一个常数)。

那么,我们能不能进一步优化该算法呢?

答案是可以的,只要我们按照上述步骤继续分下去就可以了,如下图:
在这里插入图片描述


二、快排步骤

当然快速排序也可用来给任意n个数的序列排序。但是与和1~n排序不同的是,对于任意n个数的序列,我们在划分子段的时候并不能很容易找到整个序列的“中位数”。所以只能在序列中任意取一个数。比如

  • 取整个序列中最左边的数。
  • 取整个序列中最右边的数。
  • 在整个序列中随机一个位置并取该位置上的数。

都是常见的取数策略。

但由于不能保证每次取的数字都刚好是中位数,所以每次划分时也不能保证左边子段长度和右边子段长度非常平均。如果“不幸”选到不合适的数(比如整个子段中最小的数或最大的数),整个算法的效率会降低很多。

在此,我们详细描述一下给任意n个数排序的快速排序算法:

1、假设我们要对数组a[1..n]排序。初始化区间[1..n]

2、令l和r分别为当前区间的左右端点。下面假设我们对l到r子段内的数字进行划分。取pivot = a[l]为分界线,将<pivot的数字移到左边,>pivot的数字移到右边,然后将pivot放在中间。假设pivot的位置是k

3、如果左边区间[l..k-1]长度大于1,则对于新的区间[l..k-1],重复调用上面的过程。

4、如果右边区间[k+1..r]长度大于1,则设置新的区间[k+1, r],重复调用上面的过程。

当整个过程结束以后,整个序列排序完毕。


三、代码实现

代码如下(示例):

// 该代码参考 https://www.geeksforgeeks.org/quick-sort/
#include <bits/stdc++.h>
#define N 100010 
using namespace std; 
int n; 
int a[N]; 
 
void quick_sort(int l, int r) { 
    // 设置最右边的数为分界线
    int pivot = a[r];
    
    // 元素移动
    int k = l - 1;
    for (int j = l; j < r; ++j)
        if (a[j] < pivot) swap(a[j], a[++k]); 
    swap(a[r], a[++k]); 
    
    if (l < k - 1) quick_sort(l, k - 1); // 如果序列的分界线左边的子段长度>1,排序
    if (k + 1 < r) quick_sort(k + 1, r); // 如果序列的分界线右边的子段长度>1,排序
    // 上面的过程结束后,到这里左子段和右子段已经分别排好序。又因为确定分界线以后的移动操作
    // 保证了左子段中的元素都小于等于分界线,右子段中的元素都大于分界线。所以整个序列也是有序的。
} 
 
int main() { 
    // 输入
    scanf("%d", &n); 
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); 
     
    // 快速排序
    quick_sort(1, n); 
    
    // 输出
    for (int i = 1; i <= n; ++i) printf("%d ", a[i]);  
    return 0; 
} 

四、复杂度分析

空间复杂度

首先该算法的空间复杂度是O(n),具体来说,在整个排序过程中,元素的移动都在原始数组中进行。所以快速排序是一种原地排序算法。

时间复杂度

可以看出,在「详细算法描述」中,我们的算法分为若干层。每一层中都是分治法的三个步骤:我们首先进行问题拆分,然后进入下一层,下一层的问题解决后,我们返回这一层进行子问题解的合并。
在这里插入图片描述
我们首先分析对1~nn个数字进行快速排序的情况。

在每一层中,问题拆分的复杂度是O(n),因为我们移动数组元素的时候,需要将每个子段扫一遍。那么把所有层的子段一起看,就相当于在每一层都把整个序列完整扫了一遍。对于子段解的合并,其复杂度是O(1),因为有分界线的存在,当我们把左边和右边都排好序后,它们和分界线元素一起天然形成了原序列的完整排序。

那么一共有多少层呢?因为每次我们都知道当前子段的中位数,所以可以保证每次划分,两个字段长度比较平衡,所以下一层子段的长度都比上一层减少了一半,直到长度为1算法停止。所以整个算法有logn层。

那么我们分析出在这种情况下,算法的复杂度是O(n * logn)。这样,在1秒之内,计算机能非常轻松地排序10^6 及以上的数据。

但对于任意n个数的排序,每次划分情况取决于选取的分界线情况。如果每次分界线刚好取到最小值或者最大值,会导致划分时所有数字都会移动到同一边,整个算法的复杂度也会下降为O(n^2)。如下图:
在这里插入图片描述
我们很容易想到两种尽量避免出现这种情况的方法:

  • 在排序之前,先把整个数组随机打乱顺序。
  • 在选取分界线时,与之前固定选取某个位置的方法相比,我们换成随机选择分界线的位置。

这两种方法都能极大概率避免上面提到的极端情况的发生。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++算法之快速排序 的相关文章

随机推荐

  • 【HTML】解决恶意script脚本输入问题

    项目场景 提示 这里简述项目相关背景 HTML script 安全验证 程序永远不可以相信用户的输入 问题描述 系统做安全测试 发现系统中允许直接调用用户输入的脚本内容 如 系统加载完 会重复执行这个脚本 原因分析 提示 这里填写问题的分析
  • 2021-09-30 学习记录:渐变线的制作

    如上所示渐变线 写法如下 part 4 width 320 remh height 2 remh background image linear gradient 136deg rgba 39 101 150 0 0 rgba 39 221
  • log4j.properties配置详解

    stone 的 log4j配置详解 Log4J的配置文件 Configuration File 就是用来设置记录器的级别 存放器和布局的 它可接key value格式的设置或xml格式的设置信息 通过配置 可以创建出Log4J的运行环境 1
  • 操作系统类型

    unix freebsd VxWorks Solaris Windows xp 7 8 10 Linux Redhat Ubuntu SUSE CentOS mobile Android ios symbian embeded system
  • 前端插件之 Select2 介绍及使用

    Select2是一款基于JQuery的下拉列表插件 主要用来优化select 支持单选和多选 同时也支持分组显示 列表检索 远程获取数据等众多好用的功能 项目地址 select2 org 基本使用 需要用到的JS和CSS文件位于项目代码下的
  • 彻底理解线程

    优质资源分享 学习路线指引 点击解锁 知识定位 人群定位 Python实战微信订餐小程序 进阶级 本课程是python flask 微信小程序的完美结合 从项目搭建到腾讯云部署上线 打造一个全栈订餐系统 Python量化交易实战 入门级 手
  • h2database BTree 设计实现与查询优化思考

    h2database 是使用Java 编写的开源数据库 兼容ANSI SQL89 既实现了常规基于 BTree 的存储引擎 又支持日志结构存储引擎 功能非常丰富 死锁检测机制 事务特性 MVCC 运维工具等 数据库学习非常好的案例 本文理论
  • Python爬虫(七)学习提取网页中所有链接

    import re import urllib request def getlink url headers User Agent Mozilla 5 0 Windows NT 10 0 WOW64 AppleWebKit 537 36
  • Android OpenGL ES抗锯齿

    多重采样MSAA GLSurfaceView设置多重采样 抗锯齿EGLConfigChooser author weiss email kleinminamo gmail com created 2018 7 3 public class
  • Quartz2.2.0 产生misfire条件参数misfireThreshold和misfire策略详细说明

    首先 misfire产生的条件是 misfire的时间间隔大于配置里设置的misfireThreshold值 就认为是misfire了 错失触发了 比如 13 07 24开始执行 重复执行5次 开始执行时 quartz已经计算好每次调度的时
  • rk3588:failed to open rknpu module, need to insmod rknpu dirver!

    1 permission denied sudo chmod R 777 userdata 2 sudo E 系统中已有的python test py 大概是环境变量这块的问题 sudo E一下就行了
  • 情境领导者-第二章、领导风格

    文章目录 情境领导者 第二章 领导风格 故事 背景 独裁式与民主式的领导风格 工作行为 关系行为 态度与行为 领导风格 风格一 S1 风格二 S2 风格三 S3 风格四 S4 结束语 情境领导者 第二章 领导风格 故事 罗杰斯 简单地说 我
  • Unity3D-5.4.1f-Rain Storm Effects插件应用及车辆前挡风玻璃简易雨刮器制作

    Unity3D 5 4 1f Rain Storm Effects插件应用及车辆前挡风玻璃简易雨刮器制作 作者Ghost Light 1 使用的雨天天气模拟插件是Rain Storm Effects Rain Storm Effects的基
  • mysql 字段拼接_mysql 字符串拼接,你知道几种方式?

    第一种 mysql自带语法CONCAT string1 string2 此处是直接把string1和string2等等的字符串拼接起来 无缝拼接哦 说明 此方法在拼接的时候如果有一个值为NULL 则返回NULL 如 1 SELECT CON
  • shell入门第三课 case语句

    虽然if elif语言可以做多项选择 但是使用case在有大量选择的情况下 更为合理 case语句与C语言的case有些相似 可以根据条件选择对应的语句执行 1 形式 case语句 case 变量 in 模式11 模式12 表达式 模式21
  • 区块链节点和区块区别_《区块链百问百答》-区块链的节点竞选,到底是什么...

    上期讲解的区块链在医疗领域的应用 你看了吗 本期视频将解答大家关于节点竞选的困惑 现在跟随由LemoChain策划的 区块链百问百答 来认识一下吧 https www zhihu com video 1139492556072271872
  • 删除字符串最后一个字符的几种方法

    偶然看到的 记录一下 以免忘记 字符串 string s 1 2 3 4 5 目标 删除最后一个 方法 1 用的最多的是Substring 这个也是我一直用的 s s Substring 0 s Length 1 2 用 RTrim 这个我
  • 网络安全-php安全知识点

    目录 语法与注释 输出 变量 弱类型安全 超级全局常量 函数 常用 字符串相关 正则表达式 子字符串位置 数据库相关 mysqli pdo 伪协议相关 反序列化漏洞 serialize函数 unserialize函数 魔法函数 举例 写给和
  • MYSQL——解决多维度随机组合查询场景:grouping sets函数

    一 引入 注意 通常用在构建数据集市和复杂随机组合场景查询时使用 对于经常需要对数据进行多维度的聚合分析的场景 您既需要对A列做聚合 也要对B列做聚合 同时要对A B两列做聚合 因此需要多次使用union all 案例 比如此处有一张表te
  • C++算法之快速排序

    C 算法之快速排序 文章目录 C 算法之快速排序 一 快速排序引出 二 快排步骤 三 代码实现 四 复杂度分析 一 快速排序引出 我们知道 给一个长度为n的序列排序 有三种很简单的算法 选择排序 冒泡排序 插入排序 这三种算法的复杂度均为O