【数据结构与算法】如何对快速排序进行细节优化以及实现非递归版本的快速排序?

news2024/9/23 1:32:28

在这里插入图片描述

君兮_的个人主页

即使走的再远,也勿忘启程时的初心

C/C++ 游戏开发

Hello,米娜桑们,这里是君兮_,国庆长假结束了,无论是工作还是学习都该回到正轨上来了,从今天开始恢复正常的更新频率,今天为大家带来的内容是快速排序的两大优化和非递归实现

  • 好了废话不多说,开始我们今天的学习吧!!

    快排优化与非递归实现

    • 快速排序优化
      • 三数去中优化
      • 对递归次数的优化
    • 非递归的快速排序
    • 总结

快速排序优化

  • 有关快速排序的基本内容可以去看看这篇博客,讲的已经非常详细了
    【算法速查】万字图解带你快速入门八大排序(下)
  • 我们在这里就以hoare版本的快速排序来讲讲还可以优化的地方以及为什么
  • hoare版本的快速排序代码如下:
Swap(int* p1, int* p2)
{
    int tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
}
int getMid(int* a, int left, int right)
{
    assert(a);
    int mid = (left + right) / 2;
    if (a[left] > a[mid])
    {
        if (a[mid] > a[right])
            return mid;
        else if(a[right]>a[left])
        {
            return left;
        }
        else
        {
            return right;
        }
    }
    else
    {
        if (a[mid] < a[right])
            return mid;
        else if (a[left] > a[right])
        {
            return left;
        }
        else
        {
            return right;
        }

    }
}
int PartSort1(int* a, int left, int right)
{
    int mid = getMid(a, left, right);//三数取中
    //把取好的key放到left中
    Swap(&a[left], &a[mid]);
    int key = left;
    while (left<right)
    {
    //右边先走
        while (left < right && a[right] >= a[key])
        {
            right--;
        }
      //左边走
        while (left<right&&a[left]<=a[key])
        {
            left++;
        }
        //交换
        Swap(&a[left], &a[right]);
    }
    //交换停下的位置的值把key换到此时左右相遇的位置
    Swap(&a[key], &a[left]);
    //此时key的左右都有序,由于right,left都指处于key的位置返回任意即可
    return right;
}
void QuickSort(int* a, int left,int right)
{
	//只剩下一个值了,说明已经有序,不需要再排,递归结束
    if (left >= right)
        return;
   
    int key = PartSort1(a,left,right);
    //key已经满足左右都有序了不需要再排
    //排key的左边
    QuickSort(a, left, key-1);
    //排key的右边
    QuickSort(a, key+1, right);

}

三数去中优化

  • 我们知道,快速排序是先取一个key值然后让左右两边有序来进行排序的,因此key值的取值对我们快速排序的速度是有比较大的影响的,举个最坏的例子,假设每次我们取到的key值都是此次所需排序数据中最小的,如下图所示
    在这里插入图片描述

  • 此时的时间复杂度就是O(N^2)了,因此,我们需要对快速排序进行优化,尽量减少出现图示的这种情况,就有了以下的代码

int getMid(int* a, int left, int right)
{
    assert(a);
    int mid = (left + right) / 2;
    if (a[left] > a[mid])
    {
        if (a[mid] > a[right])
            return mid;
        else if(a[right]>a[left])
        {
            return left;
        }
        else
        {
            return right;
        }
    }
    else
    {
        if (a[mid] < a[right])
            return mid;
        else if (a[left] > a[right])
        {
            return left;
        }
        else
        {
            return right;
        }

    }
}
  • 简单的来说,上述这段代码表示的是这样的意思
    取最左,最右,中间三个数,分别对三个数进行比较,最终取得的值就是处于三个值中中间的这个值。
  • 通过上述这个优化,此时所需排序的数据中总要比我们取得的key值小以及比我们取得的key值大的值存在,就能较大的提供我们的快排效率啦!

对递归次数的优化

  • 我们在使用递归版本的快速排序时,当区间中的数比较少时,仍然使用递归的方式进行是会消耗非常多不必要消耗的内存的,还是举个例子:假设此时区间中还有10个数需要排
    在这里插入图片描述
  • 我们递归返回的条件是left>=right,递归是栈中开辟空间进行的,当递归的层数过深,栈的大小又不是很大,就容易造成“爆栈”,如上图所示,为了排序这十个数,我们又递归了这么多层,是非常不明智的选择,因此,我们在数据较少的情况出现时,可以使用插入排序等方法进行排序,减少不必要的空间浪费,也能提供我们快排的速度
void QuickSort1(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	// 小区间优化,小区间不再递归分割排序,降低递归次数
	if ((end - begin + 1) > 10)
	{
		int keyi = PartSort1(a, begin, end);

		// [begin, keyi-1] keyi [keyi+1, end]
		QuickSort1(a, begin, keyi - 1);
		QuickSort1(a, keyi + 1, end);
	}
	else
	{
		InsertSort(a + begin, end - begin + 1);
	}
}

  • 好了,讲完了上述对递归版本的快排优化,接下来我们讲讲快速排序的非递归版本

非递归的快速排序

  • 我们上面讲了,递归是在栈空间中进行的,栈空间又比较小,当递归层数比较深时就会造成“爆栈”,因此对于快速排序这种我们常用的排序算法来说,掌握其非递归版本也是非常重要的
  • 想要了解非递归,我们就必须从递归开始下手,我们再来看看递归的这段代码
void QuickSort1(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	// 小区间优化,小区间不再递归分割排序,降低递归次数
	if ((end - begin + 1) > 10)
	{
		int keyi = PartSort1(a, begin, end);

		// [begin, keyi-1] keyi [keyi+1, end]
		QuickSort1(a, begin, keyi - 1);
		QuickSort1(a, keyi + 1, end);
	}
	else
	{
		InsertSort(a + begin, end - begin + 1);
	}
}

  • 如果你学过数据结构的话,会发现我们递归与栈是非常类似的,栈是后进先出,最后再处理最先放入的,而递归也是先往深处走,再往回返,因此,我们在实现非递归的快速排序时,选用栈这种数据结构来帮助我们进行。
void QuickSortNonR(int* a, int left,int right)
{
    Stack st;
    StackInit(&st);//初始化栈
    StackPush(&st, left);//入栈
    StackPush(&st, right);
    while (!StackEmpty(&st))//判断栈是否为空
    {
        int right = StackTop(&st);//后进先出,取栈顶元素
        StackPop(&st);//此时的栈顶元素出栈
        int left = StackTop(&st);//此时的栈顶为left
        StackPop(&st);
        int key = PartSort1(a, left, right);//选key值
        if (key + 1 < right)//此时key+1小于right 把key+1作为下一次排序的左 right作为右入栈
        {
            StackPush(&st, key + 1);
            StackPush(&st, right);
        }
        if (left < key - 1)//key-1大于left key-1就为下一次循环的右,left为左
        {
            StackPush(&st, left);
            StackPush(&st, key - 1);
        }

    }
    //当栈中没有元素了,说明此时的左大于等于右,此时已经没有数据未进行排序了
    StackDestroy(&st);//销毁栈
}
  • 和递归大致是一样的,只不过我们是用栈的方式来模拟递归朝深度进行,如果你能理解递归实现的快速排序,相信非递归实现的快速排序对你来说也非常好理解
  • 唯一需要注意的是入栈和出栈的顺序,当你开始先入右再入左的话,由于后进先出的原因,先出的是左其中是右,这点在取栈顶元素作为排序的左右区间时一定要注意避免取错。

总结

  • 好啦,我们总算把八大排序算法都讲完了,算法这一块光靠看代码不是那么容易理解的,因此我花了大量的时间画图分析,希望能对你有所帮助
  • 当然,这篇文章创作的初衷是希望帮助初学者对排序算法有一个大致的了解,对已经学过的人起到在需要使用的时候快速回忆的效果,因此可能还有一部分细节不全,之后我会挑出重点单独出博客讲解
  • 有任何的问题和对文章内容的疑惑欢迎在评论区中提出,当然也可以私信我,我会在第一时间回复的!!

新人博主创作不易,如果感觉文章内容对你有所帮助的话不妨三连一下再走呗。你们的支持就是我更新的动力!!!

**(可莉请求你们三连支持一下博主!!!点击下方评论点赞收藏帮帮可莉吧)**

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

将nginx注册为Windows系统服务

文章目录 1、使用nssm小工具2、使用winsw小工具2.1、下载2.2、用法2.3、重命名2.4、创建配置文件2.4.1、xml文件2.4.2、config文件&#xff08;该文件可省略&#xff09; 2.5、最终文件2.6、安装与卸载 1、使用nssm小工具 该方法最简单 首先&#xff0c;下载nssm小工具&#…

C语言——二周目——字符串与内存库函数总结

目录 一、字符串函数 1.求字符串长度——strlen 模拟实现 2.字符串拷贝函数——strcpy/strncpy 3.字符串追加函数——strcat/strncat 4.字符串比较函数——strcmp/strncmp 5.字符串查找函数——strstr 6.字符串分割函数——strtok 二、内存操作函数 1.内存拷贝函数—…

每日leetcode_2441

Leetcode每日一题_2441 记录自己的成长&#xff0c;加油。 题目 解题 class Solution {public int findMaxK(int[] nums) {int k -1;Set<Integer> set new HashSet<Integer>();for (int x : nums) {set.add(x);}for (int x : nums) {if (set.contains(-x)) {k …

linux 分区 添加 挂载centos挂载 Microsoft basic

​ 一 背景 es 忽然写不进去了 报错 TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark查看发现 磁盘已经满了 2T 的磁盘已经使用完了 ​ fdisk -l 查看原来磁盘有64000G 有64T 装机的人只分区了2T,白白浪费58T. 二 添加分区 利用剩余的空间 1添加分区 添…

光伏发电站并网新能源消纳数据采集监控监测方案

全市分布式光伏大数据平台&#xff0c;上报省级能源大数据中心。光伏电站实时运行数据&#xff1a;包括逆变器运行数据和状态、样板逆变器实时出力曲线&#xff0c;光伏电站并网点实际功率&#xff0c;气象监测数据&#xff0c;数据实时采集&#xff0c;采集频率根据光伏电站实…

Pytorch之EfficientNetV2图像分类

文章目录 前言一、EfficientNet V21. 网络简介2. EfficientNetV1弊端&#x1f947;训练图像的尺寸很大时&#xff0c;训练速度非常慢&#x1f948;在网络浅层中使用Depthwise convolutions速度会很慢&#x1f949;同等的放大每个stage是次优的 3.NAS Search4. Progressive Lear…

【C++】Vector -- 详解

一、vector的介绍及使用 1、vector的介绍 https://cplusplus.com/reference/vector/vector/ vector 是表示可变大小数组的序列容器。 就像数组一样&#xff0c;vector 也采用的连续存储空间来存储元素。也就是意味着可以采用下标对 vector 的元素进行访问&#xff0c;和数组一…

华天OA任意文件上传漏洞 复现

文章目录 华天OA任意文件上传漏洞 复现0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 0x06 修复建议 华天OA任意文件上传漏洞 复现 0x01 前言 免责声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播…

整理笔记——推挽输出、开漏输出

在使用MCU时&#xff0c;常看到配置IO口为推挽输出、开漏输出&#xff0c;以STM32为例&#xff0c;IO口有一下集中模式&#xff0c;单片机的内部电路简化图如下&#xff1a; 1.推挽输出 2.开漏输出 3.复用推挽输出 4.复用开漏输出 一、推挽输出 推挽电路的示意图&#xff1a; …

Linux系统常用指令篇---(完)

Linux系统常用指令篇—(完) 1.时间相关的指令 date显示 date 指定格式显示时间&#xff1a; date %Y:%m:%d date 用法&#xff1a; date [OPTION]… [FORMAT] 1.在显示方面&#xff0c;使用者可以设定欲显示的格式&#xff0c;格式设定为一个加号后接数个标记&#xff0c;其…

【AI】深度学习——前馈神经网络——全连接前馈神经网络

文章目录 1.1 全连接前馈神经网络1.1.1 符号说明超参数参数活性值 1.1.2 信息传播公式通用近似定理 1.1.3 神经网络与机器学习结合二分类问题多分类问题 1.1.4 参数学习矩阵求导链式法则更为高效的参数学习反向传播算法目标计算 ∂ z ( l ) ∂ w i j ( l ) \frac{\partial z^{…

ubuntu安装Chrome浏览器

切换暂存路径(具体根据自己) cd 下载/下载Chrome安装包 wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb安装Chrome浏览器 sudo dpkg -i google-chrome-stable_current_amd64.deb更新包 非必须 sudo apt-get -f install 启动Chrome浏览器 …

FLIP动画做拖拽排序效果

先来看效果 index.html文件 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport" content&quo…

系统架构设计:1论软件系统建模方法及其应用(试读)

目录 一 软件系统建模方法 1结构化建模 2信息工程建模 3面向对象建模 4功能分解法 5基于构件的开发方法 一 软件系统建模方法 软件建模体现了软件设计的思想&#xff0c;是连接需求和实现的桥梁&#xff0c;用于指导软件的具体实现。软件模型不是软件系统的完备表示&…

【Pytorch模型可视化】Netron可视化pytorch框架下的模型

文章目录 1. 下载Netron2. pth/pt转onnx3. 可视化实现 1. 下载Netron github 下载链接&#xff1a;https://github.com/lutzroeder/Netron 打开上面网页&#xff0c;显示界面中的 Windows&#xff1a;Download 处点击直接下载 .exe 可执行文件。 如果觉得下载麻烦&#xff0c…

常见的通用型项目管理软件推荐

常用项目管理软件有很多。按适合的行业来分&#xff0c;有针对各种垂直行业定制的&#xff0c;也有全行业通用的。从厂商来看&#xff0c;有国外研发的&#xff0c;也有国内厂商的&#xff0c;体现在软件的设计思路不同&#xff0c;上手的手感也很不一样。从体量来看&#xff0…

dom-to-image库是如何将html转换成图片的

dom-to-image库可以帮你把dom节点转换为图片&#xff0c;它的核心原理很简单&#xff0c;就是利用svg的foreignObject标签能嵌入html的特性&#xff0c;然后通过img标签加载svg&#xff0c;最后再通过canvas绘制img实现导出&#xff0c;好了&#xff0c;本文到此结束。 另一个…

SpringBoot之使用Redis和注解实现接口幂等性

文章目录 1 接口幂等性1.1 概念1.2 实现思路1.3 代码实现1.3.1 pom1.3.2 JedisUtil1.3.3 自定义注解ApiIdempotent1.3.4 ApiIdempotentInterceptor拦截器1.3.5 TokenServiceImpl1.3.6 TestApplication 1.4 测试验证1.4.1 获取token的控制器TokenController1.4.2 TestController…

[ICCV-23] DeformToon3D: Deformable Neural Radiance Fields for 3D Toonification

pdf | code 将3D人脸风格化问题拆分为几何风格化与纹理风格化。提出StyleField&#xff0c;学习以风格/ID为控制信号的几何形变残差&#xff0c;实现几何风格化。通过对超分网络引入AdaIN&#xff0c;实现纹理风格化。由于没有修改3D GAN空间&#xff0c;因此可以便捷实现Edit…

Matlab导入log(或txt)文件,提取数据或其他信息

导入log&#xff08;或txt)文件 先上代码&#xff1a; clc; clear; %数据导入 file1fullfile(文件路径, test1.log); % 导入文件test1.log f1fopen(file1); dt1textscan(f1,%s); %采用textscan 读取数据 %得到的dt1是元胞数组格式比如&#xff1a;有如下内容文件 导入…