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

数据结构——排序算法第二幕(交换排序:冒泡排序、快速排序(三种版本) 归并排序:归并排序(分治))超详细!!!!

在这里插入图片描述

文章目录

  • 前言
  • 一、交换排序
    • 1.1 冒泡排序
    • 1.2 快速排序
      • 1.2.1 hoare版本 快排
      • 1.2.2 挖坑法 快排
      • 1.2.3 lomuto前后指针 快排
  • 二、归并排序
  • 总结

前言

继上篇学习了排序的前面两个部分:直接插入排序选择排序
今天我们来学习排序中常用的交换排序以及非常稳定的归并排序
快排可是有多种方法的,高速列车,即将发车,fellow me

一、交换排序

交换排序基本思想:
所谓交换,就是根据序列中两个记录键值的比较结果对换这两个记录在序列中的位置
交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动

1.1 冒泡排序

冒泡排序是一种最基础的交换排序。之所以叫做冒泡排序,因为每一个元素都可以像小气泡一样,根据自身大小一点一点向数组的一侧移动。这个算法在平常算法题中基本不用(因为太慢了),只能说具有教学意义。
就简单实现一下代码啦

void BubbleSort(int* a,int n)
{int exchange = 0;for (int i = 0; i < n; i++){for (int j = 0; j < n - 1 - i; j++){if (a[j] > a[j + 1]){exchange = 1;swap(a[j], a[i]);}}if (!exchange)break;}
}

冒泡排序的特性总结
时间复杂度: O(N^2)
空间复杂度: O(1)

1.2 快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

快速排序实现主框架:
其实快排主要就是递归,把一个大区间不断划分成子区间

void QuickSort(int* a, int left, int right)
{if (left >= right){return;}//_QuickSort用于按照基准值将区间[left,right)中的元素进行划分int meet = _QuickSort(a, left, right);QuickSort(a, left, meet - 1);QuickSort(a, meet + 1, right);
}

1.2.1 hoare版本 快排

算法思路 :
创建左右指针,确定基准值
从右向左找出比基准值小的数据,从左向右找出比基准值大的数据,左右指针数据交换,进入下次循环
其实就是确定一个数为基准值,然后根据基准值,把当前区域的数据分成两部分,左边小于基准值,右边大于基准值
然后再递归到分好的区域,继续重复操作,一个大问题划分成无数个一样的子问题。

讲到这里大概都知道怎么写啦,上代码

int _QuickSort(int* a, int left, int right)
{int keyi = left;   //  先定义区间第一个数为基准值   left++;  // left++  对除基准值以外的数据进行判断操作   while (left <= right)   //   只要left<right  就继续循环   {					//   我们这里right是找比基准值小的数据   left是找比基准值大的数据   然后进行调换  while (left <= right && a[right] > a[keyi])//  当右边的值大于基准值时  right--  直到找到小于基准值的再跳出循环{right--;}while (left <= right && a[left] < a[keyi])//  当左边的数据小于基准值时  left++  直到找到大于基准值的再跳出循环{left--;}    //   两个循环跳出后  left对应的数据大于基准值  right对应的数据小于基准值  //   对数据进行调换   这样就把小的放在左边  大的放在右边   if (left <= right)   {swap(a[right--], a[left++]);}}   //   当left>right的时候  交换最开始的基准值的位置 这个时候新的基准值就取right  swap(a[right], a[keyi]);return right;    //   到此  新的区间划分就处理好了   新的基准值返回就好啦  
}

第一个版本实现完毕了,可能会有些疑问

为什么跳出循环后right位置的值一定不大于key?
当left > right 时,即right走到left的左侧,而left扫描过的数据均不大于key,因此right此时指向的数据一定不大于key

可以试着自己模拟一下下面的流程图
在这里插入图片描述

问题2:为什么left 和 right指定的数据和key值相等时也要交换?
相等的值参与交换确实有一些额外消耗。实际还有各种复杂的场景,-假设数组中的数据大量重复时,相等也交换能进行有效的分割排序。

在这里插入图片描述

如果不相等才交换的话,假设数据全是相同的数据,那每次基准值只能找到初始基准值的下一个,时间复杂度会变成O(N^2)

快排是挺快的,但好东西总有缺陷

快排 :hoare版本的时间复杂度
划分区间递归的时间复杂度为 logn
每次区间内找新的基准值时间复杂度为 n
时间复杂度为 N*logN
但是在数据有序的时候,时间复杂度还是O(N^2),新的基准值只能找到key的下一个数字,划分区间的效率很低

1.2.2 挖坑法 快排

思路:
创建左右指针。首先从右向左找出比基准小的数据找到后立即放入左边坑中当前位置变为新的"坑"然后从左向右找出比基准大的数据找到后立即放入右边坑中当前位置变为新的"坑",结束循环后将最开始存储的分界值放入当前的"坑"中,返回当前"坑"下标(即分界值下标)。
就是先从右往左找,再从左往右找,不断循环,直到left>right,过程中数值一直在迭代交换,这个时候最后一个坑刚好放最开始挖的值。

相比hoare还是有差别的
在这里插入图片描述

int _QuickSort1(int* a, int left, int right)
{int hole = left;   //  找到第一个坑  int key = a[left];  //   把第一个坑保存起来   while (left < right)   //  left==right时跳出循环  最后一个坑{while (left < right && a[right] >= key)   //  从右开始往左找{right--;}a[hole] = a[right];   //  当right--的循环跳出后  这个时候right对应的值小于key  把当前right的值换到坑里hole = right;  				// right变成新的坑  while (left < right && a[left] <= key)  //  从左开始往右找  {left++;     }a[hole] = a[left];   //  当left++的循环跳出后  这个时候left对应的值大于key  把当前left的值换到坑里  hole = left;		//  left变成新坑  }a[hole] = key;   //  大循环结束后  left=rightreturn hole;    //  这个新坑留给一开始的key值   返回新的基准值下边  
}

挖坑法完毕

挖坑法和hoare版本的时间复杂度一样 n*logn
但是在特殊情况也会有不好的地方 在数据有序的时候 时间复杂的还是会变成 O(N^2)

1.2.3 lomuto前后指针 快排

创建前后指针,从左往右找比基准值小的进行交换,使得小的都排在基准值的左边。
前后指针是我认为最好理解,也是代码最简单的一个
就是定义一个cur指针向前走,一个prev指针在后面跟着,cur找比基准值小的数据
在这里插入图片描述

在这里插入图片描述
话不多说,上代码

int _QuickSort1(int* a, int left, int right)
{int prev = left;   //  定义prev  cur  指针  int key = left;int cur = left + 1;while (cur <= right){if (a[cur] < a[key] && ++prev != cur)   //  当cur对应的值小于key时,可以考虑将prev与cur对应的值交换{										//  但如果这个时候,cur刚好是prev的下一个值,时没有必要交换的swap(a[prev], a[cur]);				//  所以要判断  prev++与cur是否相等  }++cur;   //  每次循环  cur++一次   }swap(a[key], a[prev]); // 循环结束之后,prev对应的值时小于key的prev的下一个就是大于key的  这个时候调换key和prev的值return prev;			//		找到新的基准值下标返回
}								

仔细了解前后指针的流程,想必也会感觉到,当数据有序或者是全部相同时
前后指针也是O(N^2)的时间复杂度,比起hoare和挖坑法 缺陷又多了一个数据全部相同时
想想数据全部相同或者有序,其实也没有排序的需要了,除非是算法题卡了数据相同的样例
所以快排的三种方法还是可行的

快速排序特性总结:
时间复杂度: O(nlogn)
空间复杂度: O(logn)

快排的基本内容就到这里啦


二、归并排序

归并排序算法思想:
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
在这里插入图片描述
底层核心还是递归,把一个大区间逐渐分成无数个小区间,(一个大问题分成无数个相同的子问题),快排和归并都是用到了递归,想想递归真的好用。

博客链接:合并两个有序数组

还有一个问题就是在递归到最后一层之后,怎么合并两个子区间让他们有序,这里我想到前面我们做过的习题,上面的链接供参考。

话不多说上代码

void _MergeSort(int* arr, int left, int right, int* tmp)  //  把大区间分配数个小空间  
{														// 两个小空间  排序成一个空间  用tmp接受  返回赋值给原数组 		if (left >= right)  //  递归出口   {return;}int mid = left + (right - left) / 2;//  分成两个区间  采用二分_MergeSort(arr, left, mid, tmp);     // 左区间_MergeSort(arr, mid + 1, right, tmp);// 右区间  //  递归处理之后  现在就是合并两个子区间  使他们有序  int begin1 = left, end1 = mid;       //  第一个区间的begin和endint begin2 = mid + 1, end2 = right;  // 第二个区间的  begin和endint index = begin1;    //  新的下标  对应tmp数组  while (begin1 <= end1 && begin2 <= end2)   //  合并两个数组的流程  不多赘述啦{if (arr[begin1] < arr[begin2]){tmp[index++] = arr[begin1++];}else{tmp[index++] = arr[begin2++];}}while (begin1 <= end1)      //  有数组没有全部传给tmp的情况  {tmp[index++] = arr[begin1++];}while (begin2 <= end2){tmp[index++] = arr[begin2++];}for (int i = left; i <= right; i++)  //  赋值返回原数组 {arr[i] = tmp[i];}
}
void MergeSort(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int)*n);   //  我们这里传一个新开的tmp数组空间进去,辅助合并两个子区间_MergeSort(arr, 0, n - 1, tmp);free(tmp);
}

仔细回看,归并其实也不难,就是一个递归的处理,然后再合并两个区间而已,洒洒水啦

实话实说,归并稳定,时间复杂度一直是O(nlogn) 不管数据是否有序是否相同
归并排序特性总结:
时间复杂度: O(nlogn)
空间复杂度: O(n)

总结

都说快排是个大家伙,现在学完来看,也就一般般嘛
回顾今天学习的内容,从快排的三种方式,到递归合并的归并排序
差不多都是围绕递归在展开排序,虽然快排有些许缺陷,但影响不大
现在想想,归并排序,又稳又好,就是代码有点多 哈哈哈哈
今天的学习就到这里啦,下一篇将深究一下快排以及非递归实现快排,不要走开,小编持续更新中~~~~

有差错的地方还请各位指出,小编必然马不停蹄来修改~~~~

在这里插入图片描述

相关文章:

数据结构——排序算法第二幕(交换排序:冒泡排序、快速排序(三种版本) 归并排序:归并排序(分治))超详细!!!!

文章目录 前言一、交换排序1.1 冒泡排序1.2 快速排序1.2.1 hoare版本 快排1.2.2 挖坑法 快排1.2.3 lomuto前后指针 快排 二、归并排序总结 前言 继上篇学习了排序的前面两个部分:直接插入排序和选择排序 今天我们来学习排序中常用的交换排序以及非常稳定的归并排序 快排可是有多…...

【kafka04】消息队列与微服务之Kafka 图形工具

Kafka 在 ZooKeeper 里面的存储结构 topic 结构 /brokers/topics/[topic] partition结构 /brokers/topics/[topic]/partitions/[partitionId]/state broker信息 /brokers/ids/[o...N] 控制器 /controller 存储center controller中央控制器所在kafka broker的信息 消费者 /c…...

剖析前后端 API 接口参数设计:JSON 数据结构化全攻略

在当今软件开发领域&#xff0c;前后端分离架构已成为主流趋势。而 API 接口作为前后端之间数据交互的桥梁&#xff0c;其设计的合理性对系统的可维护性和扩展性起着至关重要的作用。JSON&#xff08;JavaScript Object Notation&#xff09;作为一种轻量级的数据交换格式&…...

vue3 多种方式接受props,定义ref,reactive

定义props 1 第一种 interface AddType { dialogStudyVisible: boolean; } const props defineProps<AddType>(); 第二种 // const props defineProps({ // dialogStudyVisible:{ // type:Boolean, // default:false // } // }) 第三种 // const …...

逻辑处理器核心指纹修改

navigator.hardwareConcurrency的属性,可以用来获取CPU的逻辑处理器核心数。 1、navigator.hardwareConcurrency接口定义&#xff1a; third_party\blink\renderer\core\frame\navigator_concurrent_hardware.idl // https://html.spec.whatwg.org/C/#navigator.hardwarecon…...

如何制作项目网页

一、背景 许多论文里经常会有这样一句话Supplementary material can be found at https://hri-eu.github.io/Lami/&#xff0c;这个就是将论文中的内容或者补充视频放到一个网页上&#xff0c;以更好的展示他们的工作。因此&#xff0c;这里介绍下如何使用前人提供的模板制作我…...

mongodb/redis/neo4j 如何自己打造一个 web 数据库可视化客户端?

随笔 从千万粉丝“何同学”抄袭开源项目说起&#xff0c;为何纯技术死路一条&#xff1f; 数据源的统一与拆分 监控报警系统的指标、规则与执行闭环 我们的系统应该配置哪些监控报警项&#xff1f; 监控报警系统如何实现自监控? java 老矣&#xff0c;尚能饭否&#xff…...

1、正则表达式

grep匹配 grep用来过滤文本内容&#xff0c;以匹配要查询的结果。 grep root /etc/passwd&#xff1a;匹配包含root的行 -m 数字&#xff1a;匹配几次后停止 -v&#xff1a;取反-i&#xff1a;忽略字符的大小写&#xff0c;默认的&#xff0c;可以不加-n&#xff1a…...

Airsim安装问题:This project was made with a different version of the Unreal Engine.

本文记录如何在 Ubuntu 18.04 系统中配置 AirSim 和 Unreal Engine 4.27&#xff0c;并成功打开默认的 Blocks 环境项目。 环境说明 系统&#xff1a;Ubuntu 18.04Unreal Engine 版本&#xff1a;4.27AirSim&#xff1a;主分支文件路径&#xff1a; Unreal Engine&#xff1a…...

java八股-分布式服务的接口幂等性如何设计?

文章目录 接口幂等token Redis分布式锁 原文视频链接&#xff1a;讲解的流程特别清晰&#xff0c;易懂&#xff0c;收获巨大 【新版Java面试专题视频教程&#xff0c;java八股文面试全套真题深度详解&#xff08;含大厂高频面试真题&#xff09;】 https://www.bilibili.com/…...

vscode python code runner执行乱码

打开vscode code runner插件配置&#xff0c;如图所示&#xff1a; 然后在setting.json修改运行python的默认命令&#xff1a; 将原来 替换成 "python":"set PYTHONIOENCODINGutf8 && python", 参考&#xff1a;Vscode——python环境输出中文乱…...

Java中的继承详解

在Java编程中&#xff0c;继承&#xff08;Inheritance&#xff09;是一种面向对象编程&#xff08;OOP&#xff09;的核心概念&#xff0c;它允许一个类&#xff08;称为子类或派生类&#xff09;继承另一个类&#xff08;称为父类或基类&#xff09;的属性和方法。通过继承&a…...

kafka进阶_2.存储消息

文章目录 一、存储消息介绍二、副本同步2.1、数据一致性2.2、HW在副本之间的传递 如果想了解kafka基础架构和生产者架构可以参考 kafka基础和 Kafka进阶_1.生产消息。 一、存储消息介绍 数据已经由生产者Producer发送给Kafka集群&#xff0c;当Kafka接收到数据后&#xff0c…...

如何启用本机GPU硬件加速猿大师播放器网页同时播放多路RTSP H.265 1080P高清摄像头RTSP视频流?

目前市面上主流播放RTSP视频流的方式是用服务器转码方案&#xff0c;这种方案的好处是兼容性更强&#xff0c;可以用于不同的平台&#xff0c;比如&#xff1a;Windows、Linux或者手机端&#xff0c;但是缺点也很明显&#xff1a;延迟高、播放高清或者同时播放多路视频视频容易…...

如何更好地设计SaaS系统架构

SaaS&#xff08;Software as a Service&#xff09;架构设计的核心目标是满足多租户需求、支持弹性扩展和高性能&#xff0c;同时保持低成本和高可靠性。一个成功的SaaS系统需要兼顾技术架构、资源利用、用户体验和商业目标。本文从以下几个方面探讨如何更好地设计SaaS系统架构…...

表征对齐在训练DiT模型中的重要性

Diffusion Models专栏文章汇总&#xff1a;入门与实战 前言&#xff1a;训练过DiT模型的读者们肯定有所体会&#xff0c;相比于UNet模型训练难度大了很多&#xff0c;模型不仅很难收敛&#xff0c;而且非常容易训崩&#xff0c;其中一个很重要的原因是没有进行表征对齐&#xf…...

Qt中CMakeLists.txt解释大全

‌Qt从Qt5.15版本开始正式推荐使用CMake进行项目管理‌。 在Qt 5.15之前&#xff0c;虽然可以使用CMake进行构建&#xff0c;但Qt官方更推荐使用qmake。 然而&#xff0c;从Qt5.15开始&#xff0c;Qt官方正式推荐使用CMake作为主要的构建系统&#xff0c;并在Qt 6中进一步加强了…...

【在 PyTorch 中使用 tqdm 显示训练进度条,并解决常见错误TypeError: ‘module‘ object is not callable】

在 PyTorch 中使用 tqdm 显示训练进度条&#xff0c;并解决常见错误TypeError: module object is not callable 在进行深度学习模型训练时&#xff0c;尤其是在处理大规模数据时&#xff0c;实时了解训练过程中的进展是非常重要的。为了实现这一点&#xff0c;我们可以使用 tq…...

数据结构-堆的实现和应用

目录 1.堆的概念 2.堆的构建 3.堆的实现 4.堆的功能实现 4.1堆的初始化 4.2堆的销毁 4.3堆的插入 4.3.1向上调整 4.4堆的删除 4.4.1向下调整法 ​编辑4.5取堆顶 5. 向上调整法和向下调整法比较 6.堆的应用 6.1TOP-K问题 6.2TOP-K思路 6.2.1用前n个数据来建堆 6.…...

数据分析的尽头是web APP?

数据分析的尽头是web APP&#xff1f; 在做了一些数据分析的项目&#xff0c;也制作了一些数据分析相关的web APP之后&#xff0c;总结自己的一些想法和大家分享。 1.web APP是呈现数据分析结果的另外一种形式。 数据分析常见的结果是数据分析报告&#xff0c;可以是PPT或者…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天&#xff0c;再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至&#xff0c;这不仅是开发者的盛宴&#xff0c;更是全球数亿苹果用户翘首以盼的科技春晚。今年&#xff0c;苹果依旧为我们带来了全家桶式的系统更新&#xff0c;包括 iOS 26、iPadOS 26…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术&#xff0c;说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号&#xff08;调制&#xff09; 把信息从信号中抽取出来&am…...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

JVM虚拟机:内存结构、垃圾回收、性能优化

1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...

Ubuntu系统多网卡多相机IP设置方法

目录 1、硬件情况 2、如何设置网卡和相机IP 2.1 万兆网卡连接交换机&#xff0c;交换机再连相机 2.1.1 网卡设置 2.1.2 相机设置 2.3 万兆网卡直连相机 1、硬件情况 2个网卡n个相机 电脑系统信息&#xff0c;系统版本&#xff1a;Ubuntu22.04.5 LTS&#xff1b;内核版本…...

五子棋测试用例

一.项目背景 1.1 项目简介 传统棋类文化的推广 五子棋是一种古老的棋类游戏&#xff0c;有着深厚的文化底蕴。通过将五子棋制作成网页游戏&#xff0c;可以让更多的人了解和接触到这一传统棋类文化。无论是国内还是国外的玩家&#xff0c;都可以通过网页五子棋感受到东方棋类…...

内窥镜检查中基于提示的息肉分割|文献速递-深度学习医疗AI最新文献

Title 题目 Prompt-based polyp segmentation during endoscopy 内窥镜检查中基于提示的息肉分割 01 文献速递介绍 以下是对这段英文内容的中文翻译&#xff1a; ### 胃肠道癌症的发病率呈上升趋势&#xff0c;且有年轻化倾向&#xff08;Bray等人&#xff0c;2018&#x…...