快速排序(详解)c++

快速排序(Quick Sort),既然敢起这样的名字,说明它是常⻅排序算法中较为优秀的。事实上,在很多情况下,快排确实是效率较⾼的算法;c++的排序是以快排为基础,再加上堆排和插入排序做优化实现的,我们这里实现的快速排序,只是单纯的快速排序

核心算法原理可以分为两步

  1. 从待排序区间中选择一个基准元素,按照基准元素的大小将区间分成左右两部分
  2. 然后递归处理左区间和右区间,直到区间长度为1

图中第一个长方形是一个待排序区间,我们会从中选择一个基准元素p,把区间划分成小于基准元素的和大于基准元素的,如果等于基准元素就放到左边或者右边,分完区后,此时p元素已经放在排序好的最终位置上了;比如最后是个升序,左边全是小于p的,右边全是大于p的,不管左右两边是否有序,此时p元素一定放在了它的最终位置上,所以就不用管p元素了,接下来处理小于和大于基准元素的区间

再从小于基准元素区间中选择一个基准元素p,并且划分左右区间,此时p再次放到了排好序的最终位置上,接着处理它的左右区间,一直处理到不能再处理的时候,也就是区间长度为1的时候,小于基准元素区间也就是初始p元素的左区间就已经变成有序序列了,右边的大于基准元素区间也是同理

大家可以想一想处理左区间的时候,每处理一次会把一个数放到它最终的位置上,处理右区间也是,一直处理到不能再处理的时候,每个数都放到了排序好的最终位置上,他就已经变成一个有序序列了

                                   

模拟快排过程,会发现有一点先序遍历的味道,把根节点看成待排序区间,它划分出了左右区间也就是左右子树,先处理左区间,再划分左右区间,先处理左区间,把左边的例子5代入过来,此时区间长度为1就不用管它了,返回去处理右区间,只有一个节点30再返回去处理根节点的右区间,其中一个元素现在变得有序,划分出来左区间,两个元素现在变得有序,再划分出来左区间42,三个元素变得有序,因为它是一个叶子节,返回,整个序列变得有序了

先序遍历测试:

#include <iostream>
#include <vector>
using namespace std;

struct Node {
    int val;
    Node* left;
    Node* right;
    Node(int x) : val(x), left(nullptr), right(nullptr) {}
};

void preorder(Node* root) {
    if (!root) return;
    cout << root->val << " ";   // 访问根节点
    preorder(root->left);  // 遍历左子树
    preorder(root->right); // 遍历右子树
}

int main()
{
    Node* root = new Node(33);
    root->left = new Node(25);
    root->right = new Node(50);
    root->left->left = new Node(5);
    root->left->right = new Node(30);
    root->right->left = new Node(49);
    root->right->left->left = new Node(42);

    //先序遍历
    preorder(root);
    cout << endl;

    return 0;
}

朴素快排的缺陷

                           

  1. 基准元素选择不当,递归层数会增加,时间复杂度变高;比如快排一个有序序列1234567,首先划分1和右区间234567接着划分2和右区间34567,以此类推,直到划分到n层,在做数组划分的时候会遍历整个数组一遍,此时时间复杂度就是O(N)级别
  2. 当有大量重复元素时,递归层数也会增加;比如数组里面全都是6,不管选择哪一个位置的6为基准元素,选择完后都会画划分大于等于6的区间出来,之后再选择再划分,一直划分到数组元素个数的层数就和上面的情况一样了

优化一:随机选择基准元素

先解决第一个问题:基准元素选择不当,递归层数会增加,时间复杂度变高。
解决方案:在待排序区间中,随机选择一个基准元素。利用C++提供的随机函数,在一个区间内随机选择一个元素作为基准

随机函数:

srand(time(0)):种下一个随机数种子:
rand():获得一个随机数;
rand()%(right-left +1)+ left:在 [left,right]区间内,随机选择一个数

 随机函数的小demo

  • 种下一个随机数种子之后,r1r2r3可以获得不同的随机数,在自己创建的函数里面也可以获取随机数
#include <iostream>
#include <ctime>
using namespace std;

void test()
{
	int r4 = rand();
	cout << "r4 = " << r4 << '\n';
}

int main()
{
	srand(time(0)); //种下一个随机数种子

	int r1 = rand();
	int r2 = rand();
	int r3 = rand();

	cout << "r1 = " << r1 << '\n';
	cout << "r2 = " << r2 << '\n';
	cout << "r3 = " << r3 << '\n';
	test();

	//r1 = 30775
	//r2 = 31851
	//r3 = 10201
	//r4 = 12957

	return 0;
}

优化代码:

  • 让随机数模上元素个数(最后一个位置减去第一个位置+1 ),模出来的结果再+1,防止模出来的结果不在此区间;比如23456这五个位置,6-2+1等于5,区间是[0,4],再加上左区间的端点2,此时区间是[2,6],在2到6之间随便取一个数
//优化一:随机选择基准元素
//递归的时候需要知道要处理的是哪个区间
int get_random(int left, int right)
{
    return a[rand() % (right - left + 1) + left];
}

优化二:荷兰国旗-数组分三块

解决第二个问题:当有大量重复元素时,递归层数也会增加。
回忆一下之前在顺序表做过的一道题:《颜色分类》leetcode 75.颜色分类(详解)数组分块c++-CSDN博客,这道题的核心就是数组分成三块:左边全是 0,中间全是1,右边全是2,右边全部大于基准元素。接下来仅需如果在此基础上稍作修改,变成:左边全部小于基准元素;中间全部等于基准元素,递归处理左右区间,中间区间就可以无需考虑

比如数组里面全都是6,根据荷兰国旗思想会选择把全部的6放到等于基准元素区间上,它的左右两边就没有区间了,此时递归直接结束,用O(N)的时间复杂度就把整个数组变得有序了

优化代码:

测试链接:P1177 【模板】排序 - 洛谷

  • 递归过程(假设p分别是4,2)
#include <iostream>
#include <ctime>
using namespace std;

const int N = 1e5 + 10;
int n;
int a[N];

// 优化一:随机选择基准元素
int get_random(int left, int right)
{
    // 随机数范围在 [left, right] ,模元素个数 + 第一个位置
    return a[rand() % (right - left + 1) + left];
}

void quick_sort(int left, int right)
{
    // 区间长度不存在或者=1 返回
    if (left >= right) return;

    //1.选择一个基准元素
    int p = get_random(left, right);

    //2.数组分三块 = 荷兰国旗问题
    int l = left - 1, i = left, r = right + 1;
    while (i < r) //i和r不相遇,一直遍历
    {
        //分类讨论
        if (a[i] < p) swap(a[++l], a[i++]); //当前位置与l+1位置交换
        else if (a[i] > p) swap(a[--r], a[i]); //交换完后i还要继续判断当前位置,比如a[i] < p,所以i不动
        else ++i; //属于基准元素。就应该放在当前位置,直接i++
    }
    
    //处理左右区间
    // [left,1] [l+1,r-1] [r,right]
    //   < p        p        > p
    quick_sort(left, l);
    quick_sort(r, right);
}

int main()
{
    srand(time(0));
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];

    quick_sort(1, n);

    for (int i = 1; i <= n; i++) cout << a[i] << " ";

    return 0;
}   

时间复杂度:

  • 如果每次基准元素都选择得当,数组划分的比较均匀,时间复杂度=递归层数*N=N*logN
  • 如果划分不当,数组分布比较极端,时间复杂度退化成 N2

《算法导论》中有严谨的证明,加上优化之后的快排,时间复杂度能趋近于N*logN。因此,相较于之前的排序算法,快速是较优的。

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

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

相关文章

【工具变量】公司企业数字领导力(2004-2023年)

数据简介&#xff1a;企业数字化领导力是指在数字经济时代&#xff0c;领导者通过战略性地使用数字资产、引领组织变革&#xff0c;使企业在数字化环境中获得持续成功的能力。对于上市公司而言&#xff0c;这种领导力尤为重要&#xff0c;因为它直接关系到企业的战略方向、市场…

浅谈新能源汽车充电桩建设问题分析及解决方案

摘要&#xff1a; 在全球倡导低碳减排的大背景下&#xff0c;新能源成为热门行业在全球范围内得以开展。汽车尾气排放会在一定程度上加重温室效应&#xff0c;并且化石能源的日渐紧缺也迫切对新能源汽车发展提出新要求。现阶段的新能源汽车以电力汽车为主&#xff0c;与燃油汽…

seacmsv9报错注入

1、seacms的介绍 ​ seacms中文名&#xff1a;海洋影视管理系统。是一个采用了php5mysql架构的影视网站框架&#xff0c;因此&#xff0c;如果该框架有漏洞&#xff0c;那使用了该框架的各个网站都会有相同问题。 2、源码的分析 漏洞的部分源码如下&#xff1a; <?php …

python学习四

python运算符与表达式 表达式: Python中的表达式是一种计算结果的代码片段。它可以包 含变量、运算符、常数和函数调用,用于执行各种数学、逻辑 和功能操作 算术运算符: 比较(关系)运算符: 赋值运算符: 逻辑运算符: 位运算符: 成员运算符: 身份运算符 <

Nginx面试宝典【刷题系列】

文章目录 1、nginx是如何实现高并发的&#xff1f;2、Nginx如何处理HTTP请求&#xff1f;3、使用“反向代理服务器”的优点是什么?4、列举Nginx服务器的最佳用途。5、Nginx服务器上的Master和Worker进程分别是什么?6、什么是C10K问题?7、请陈述stub_status和sub_filter指令的…

数字可调控开关电源设计(论文+源码)

1 设计要求 在本次数字可调控开关电源设计过程中&#xff0c;对关键参数设定如下&#xff1a; &#xff08;1&#xff09;输入电压&#xff1a;DC24-26V,输出电压&#xff1a;12-24&#xff08;可调&#xff09;&#xff1b; &#xff08;2&#xff09;输出电压误差&#xf…

清华大学《AIGC发展研究3.0》

大家好&#xff0c;我是吾鳴。 AIGC已经爆火好长一段时间了&#xff0c;特别是DeepSeek的爆火&#xff0c;直接让很多之前没有体会过推理模型的人可以免费的使用上推理模型&#xff0c;同时DeepSeek产品形态也是全球首创&#xff0c;就是直接把AI的思考过程展示给你看&#xff…

模型和数据集的平台之在Hugging Face上进行模型下载、上传以及创建专属Space

模型下载 步骤&#xff1a; 注册Hugging Face平台 https://huggingface.co/ 新建一个hf_download_josn.py 文件 touch hf_download_josn.py 编写hf_download_josn.py文件 import os from huggingface_hub import hf_hub_download# 指定模型标识符 repo_id "inter…

脚本无法获取响应主体(原因:CORS Missing Allow Credentials)

背景&#xff1a; 前端的端口号8080&#xff0c;后端8000。需在前端向后端传一个参数&#xff0c;让后端访问数据库去检测此参数是否出现过。涉及跨域请求&#xff0c;一直有这个bug是404文件找不到。 在修改过程当中不小心删除了一段代码&#xff0c;出现了这个bug&#xff…

C#实现本地AI聊天功能(Deepseek R1及其他模型)。

前言 1、C#实现本地AI聊天功能 WPFOllamaSharpe实现本地聊天功能,可以选择使用Deepseek 及其他模型。 2、此程序默认你已经安装好了Ollama。 在运行前需要线安装好Ollama,如何安装请自行搜索 Ollama下载地址&#xff1a; https://ollama.org.cn Ollama模型下载地址&#xf…

Buildroot 添加自定义模块-内置文件到文件系统

目录 概述实现步骤1. 创建包目录和文件结构2. 配置 Config.in3. 定义 cp_bin_files.mk4. 添加源文件install.shmy.conf 5. 配置与编译 概述 Buildroot 是一个高度可定制和模块化的嵌入式 Linux 构建系统&#xff0c;适用于从简单到复杂的各种嵌入式项目. buildroot的源码中bui…

音视频入门基础:RTP专题(12)——RTP中的NAL Unit Type简介

一、引言 RTP封装H.264时&#xff0c;RTP对NALU Header的nal_unit_type附加了扩展含义。 由《音视频入门基础&#xff1a;H.264专题&#xff08;4&#xff09;——NALU Header&#xff1a;forbidden_zero_bit、nal_ref_idc、nal_unit_type简介》可以知道&#xff0c;nal_unit…

智慧园区后勤单位消防安全管理:安全运营和安全巡检

//智慧园区消防管理困境大曝光 智慧园区&#xff0c;听起来高大上&#xff0c;但消防管理却让人头疼不已。各消防子系统各自为政&#xff0c;像一座座孤岛&#xff0c;信息不共享、不协同。 消防设施管理分散&#xff0c;不同区域、企业的设备标准不一样&#xff0c;维护情况…

RAG(检索增强生成)原理、实现与评测方法探讨

RAG是什么&#xff1f; 看一下RAG的英文全称&#xff1a;Retrieval-Augmented Generation&#xff0c;建索、增强、生成&#xff1b;一句话串起来就是通过检索增强模型的生成&#xff0c;是的&#xff0c;这就是RAG。 RAG怎么做&#xff1f; 目前比较通用的套路是这样的&#x…

表单制作代码,登录动画背景前端模板

炫酷动效登录页 引言 在网页设计中,按钮是用户交互的重要元素之一。一个炫酷的按钮特效不仅能提升用户体验,还能为网页增添独特的视觉吸引力。今天,我们将通过CSS来实现一个“表单制作代码,登录动画背景前端模板”。该素材呈现了数据符号排版显示出人形的动画效果,新颖有…

HBuilder X安装教程(2025版)

一&#xff0c;官网下载最新包&#xff1a; 官网链接&#xff1a;HBuilderX-高效极客技巧 等待工具包&#xff0c;下载好。 二&#xff0c;安装打开工具&#xff1a; 把HBuilderX压缩包进行压缩&#xff0c;然后打开压缩后的文件夹

【算法系列】希尔排序算法

文章目录 希尔排序算法&#xff1a;一种高效的排序方法一、基本思想二、实现步骤1. 初始化增量2. 分组与排序3. 缩小增量4. 最终排序 三、代码实现四、增量序列的选择1. Shell增量序列2. Hibbard增量序列3. Sedgewick增量序列 五、时间复杂度六、总结 希尔排序算法&#xff1a;…

VMware虚拟机Mac版安装Win10系统

介绍 Windows 10是由美国微软公司开发的应用于计算机和平板电脑的操作系统&#xff0c;于2015年7月29日发布正式版。系统有生物识别技术、Cortana搜索功能、平板模式、桌面应用、多桌面、开始菜单进化、任务切换器、任务栏的微调、贴靠辅助、通知中心、命令提示符窗口升级、文…

android keystore源码分析

架构 Android Keystore API 和底层 Keymaster HAL 提供了一套基本的但足以满足需求的加密基元&#xff0c;以便使用访问受控且由硬件支持的密钥实现相关协议。 Keymaster HAL 是由原始设备制造商 (OEM) 提供的动态加载库&#xff0c;密钥库服务使用它来提供由硬件支持的加密服…

视频字幕识别和翻译

下载的视频很多不是汉语的&#xff0c;我们需要用剪映将语音识别出来作为字幕压制到视频中去。 剪映6.0以后语音识别需要收费&#xff0c;但是低版本还是没有问题。 如果想要非汉语字幕转成中文&#xff0c;剪映低版本不提供这样功能。但是&#xff0c;用剪映导出识别字幕&am…