[数据结构] 基于交换的排序 冒泡排序快速排序

标题:[数据结构] 基于交换的排序 冒泡排序&&快速排序

@水墨不写bug


(图片来源于网络) 


目录

(一)冒泡排序

优化后实现:

(二)快速排序

I、实现方法: 

(1)hoare法

hoare法实现快排:

 (2)挖坑法

挖坑法实现:

(3)双指针法 

双指针法实现: 

 II、快速排序复杂度分析:

比较完备的快速排序实现如下:


正文开始:

(一)冒泡排序

 

时间复杂度:O(N^2)

空间复杂度:O(1)

特点:数组接近有序时,可通过优化来提高效率。

稳定性:稳定

冒泡排序:

        基本思想:大数下沉,小数上浮。

        实现思路:从内循环到外循环,从一趟到多趟。

        冒泡排序通过两层循环来实现,内层循环实现其中的一趟遍历,在一趟的遍历中,需要注意要控制好左右区间边界,冒泡排序的实现方式是多样的,无论用何种实现方式,最主要的是控制好边界,以及内外层循环的衔接;以下是一种冒泡排序的写法:


void BubbleSort(vector<int>& nums)
{int n = nums.size();for (int j = 0; j < n - 1; ++j){for (int i = 0; i < n - 1 - j; ++i){if (nums[i] > nums[i + 1]){::swap(nums[i], nums[i + 1]);}}}
}

优化:

        如果在一次遍历的过程中,没有进入if()交换,这已经说明数组已经有序,可以直接停止排序。

优化后实现:


void BubbleSort(vector<int>& nums)
{int n = nums.size();for (int j = 0; j < n - 1; ++j){int ex = 0;for (int i = 0; i < n - 1 - j; ++i){if (nums[i] > nums[i + 1]){ex = 1;::swap(nums[i], nums[i + 1]);}}if (ex == 0)break;}
}


(二)快速排序

时间复杂度:O(NlogN)

空间复杂度:

特点:当数据接近有序,比如逆序的时候;或者选取的key在数据中大量存在时,快速排序时间复杂度会退化为O(N^2),这是相当严重的缺陷。

稳定性:不稳定。

        快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法。

        基本思想:任取待排序元素序列中的某元素(记为key)作为基准值,按照key值将待排序集合分割成两子序列,左子序列中所有元素均小于基准值key,右子序列中所有元素均大于基准值key。然后递归,左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

        本质就是将数组根据key值分为两部分,一部分比key大,一部分比key小。对于每一个部分,再次进行如上操作,直到数组不可再分为止。这就是递归实现的浅层次的直观理解。但是递归不是本文的重点,关于递归,我会在后面与你分享。 

I、实现方法: 

(1)hoare法

        hoare法是快排的提出者hoare提供的方法,是一种经典的实现方法。

实现原理:

        定义两个下标:左下标L  和  右下标R;

        随机选取一个key值,习惯上我们选择区间的最左侧的元素为key;

        让右下标先走,向左寻找比key小的值,找到后停下来:

 停下来后:

 左下标再向右寻找比key大的值,找到后停下来:

此时,交换两个下标对应的值:

 

接下来继续执行上述步骤(R先走),我展示关键步骤:

 两下标找到要求目标并交换:

直到遇到特殊情况:两下标相遇

 

 这时停止停止循环,将最左侧元素与相遇处的元素交换位置:
即可完成一次二分。

        接下来,对于左右区间再次进行上述操作,直到区间不可再分为止。


但是如何保证相遇处的值一定小于key呢?

对于相遇这个事件,有且仅有两种情况:

只可能是R向左移动遇到L;或者是L向右移动遇到R;

        (1)R向左移动遇到L:有两种可能情况

                        a,第一次R由于没有找到比key小的值,直接一路向左遇到left,此时left就是key,然后执行自己与自己交换:此时相遇的位置的值就是key本身。

                        b,第一次之后R向左遇到L,你要想清楚,在此之前L停止的地方是被交换后的原先R的值(它是比key小的);在这样的前提条件下,R向左移动与L相遇了,说明R没有找到小于key的元素,与L相遇后指向L的比key小的值。

        (2)L向右移动遇到R

                        R先走,在找到比key小的位置停下,在此前提下,L向右移动与R相遇了,L指向R的比key小的值。

hoare法实现快排:


void QuickSort(vector<int>& nums,int left,int right)
{//递归出口,此时区间不存在或者只有一个值if (left >= right)return;//保存左右下标的值,便于在递归时找到原来的区间边界int begin = left, end = right;//keyi来记录左区间的下标int keyi = left;while (left < right){//右下标先走,找小while (left < right && nums[keyi] <= nums[right]){--right;}//左区间再走,找大while (left < right && nums[left] <= nums[keyi]){++left;}::swap(nums[left], nums[right]);}//此时一趟完毕,将key与相遇位置交换::swap(nums[left], nums[keyi]);keyi = left;//更新keyi//递归左右区间QuickSort(nums, begin, keyi);QuickSort(nums, keyi+1, end);
}

 (2)挖坑法

实现原理:

        由于左边有坑,所以右下标先走。

        找小,找到后停下来,将找到的值放在坑中,R的位置就成为了新的坑:

此时坑在右边,左边下标先走,找大:

找到后交换,左边又成为新的坑。

以此类推,直到左右相遇,相遇的位置一定是坑,此时将key放到坑中,完成单趟:

依然是左边都是比6小,右边都是比6大,说明没有错误。

挖坑法实现:

void QuickSort_(vector<int>& nums, int left, int right)
{if (left >= right)return;int begin = left, end = right;int key = nums[left];//记住key的值int hole = left;//开始挖坑while (left < right){//右先找比key大的while (left < right && nums[right] >= key)right--;//找到后,填坑,然后挖新坑nums[hole] = nums[right];hole = right;//左找比key小的while (left < right && nums[left] <= key)left++;//找到后,填坑,然后挖新坑nums[hole] = nums[left];hole = left;}//此时相遇了,把key值放在坑里nums[hole] = key;QuickSort_(nums,begin,hole);QuickSort_(nums,hole + 1,end);
}

(3)双指针法 

 

// 设置前指针prev,指向首元素,遍历指针cur指向第二个元素,
// 接下来cur开始找小,如果没找到小,就一直往前走;

//如果找到小的了,就先停下来,然后prev往前走一步,再和cur交换值,然后cur继续向后,重复上述步骤,
// 最后cur走出数组后,循环终止!此时prev指向的位置和keyi的位置交换。

我们详细看一下过程:

        

双指针法实现: 


void QuickSort__(vector<int>& nums, int left, int right)
{if (left >= right)return;int begin = left, end = right;int prev = left;int cur = left + 1;int keyi = left;while (cur <= right)//cur走出数组循环停止{//cur一直在走,如果遇到比keyi小的,就停下来等perv走一步后交换,再接着走if (nums[cur] < nums[keyi] && ++prev != cur)swap(nums[prev], nums[cur]);cur++;}//cur出去后,prev的值和keyi交换swap(nums[keyi], nums[prev]);QuickSort__(nums,left,keyi);QuickSort__(nums,keyi+1,end);}

 II、快速排序复杂度分析:

        传统的快速排序在处理一些极端问题时会显得无力,接下来我们就来讨论这些极端情况,并且给出应对极端情况的方法:

1.对于选择key不合适的问题:

        在数组元素接近逆序的时候,由于我们总是在区间的最左侧选取key,如果数组接近逆序,这时选取的key无法有效的将数组分为两个等大的数组,这就导致一次只能排序一个元素,这导致快速排序的复杂度会退化为O(N^2),这就需要我们不能随便取key。可以通过随机数法在区间内随机取一个元素作为key即可。

2.关于选择key大量出现的问题:

        如果key大量出现,也会导致上述的情况,一次只能排序一个数,所以我们不能随便分区间,这就要求我们将数组分为三部分,左侧区间都小于key,中间区间则是数值等于key的元素,右侧区间是大于key的数值。

比较完备的快速排序实现如下:

class Solution {
public:vector<int> sortArray(vector<int>& nums) {srand(time(NULL));qsort(nums,0,nums.size()-1);return nums;}void qsort(vector<int>& nums,int l,int r){//递归出口if(l >= r) return;//数组分三块int key = GetRandom(nums,l,r);int cur = l,left = l-1,right = r+1;while(cur < right){if(nums[cur] < key) swap(nums[++left],nums[cur++]);else if(nums[cur] == key) cur++;else swap(nums[--right],nums[cur]); }//递归排序子区间qsort(nums,l,left);qsort(nums,right,r);}int GetRandom(vector<int>& nums,int left,int right){return nums[ rand() % ( right - left + 1 ) + left];}
};

 完~

未经作者同意禁止转载

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

24-7-6-读书笔记(八)-《蒙田随笔集》[法]蒙田 [译]潘丽珍

文章目录 《蒙田随笔集》阅读笔记记录总结 《蒙田随笔集》 《蒙田随笔集》蒙田&#xff08;1533-1592&#xff09;&#xff0c;是个大神人&#xff0c;这本书就是250页的样子&#xff0c;但是却看了好长好长时间&#xff0c;体会还是挺深的&#xff0c;但看的也是不大仔细&…

C++笔试强训2

文章目录 一、选择题二、编程题 一、选择题 和笔试强训1的知识点考的一样&#xff0c;因为输出的是double类型所以后缀为f,m.n对其30个字符所以m是30&#xff0c;精度是4所以n是4&#xff0c;不加符号默认是右对齐&#xff0c;左对齐的话前面加-号&#xff0c;所以答案是-30.4f…

如何使用HippoRAG增强LLM的记忆

大型语言模型&#xff08;LLM&#xff09;已经证明是一种非常宝贵的思考工具。经过大量文本、代码和其他媒体数据集的训练&#xff0c;它们能够创作出接近人类水平的文章、翻译语言、生成图像&#xff0c;还能以信息丰富的方式回答人们提出的问题&#xff0c;甚至可以编写不同类…

容器:stack

以下是关于stack容器的一些总结&#xff1a; stack容器比较简单&#xff0c;主要包括&#xff1a; 1、构造函数&#xff1a;stack [staName] 2、添加、删除元素: push() 、pop() 3、获取栈顶元素&#xff1a;top() 4、获取栈的大小&#xff1a;size() 5、判断栈是否为空&#x…

Buuctf之SimpleRev做法

首先&#xff0c;查个壳&#xff0c;64bit&#xff0c;那就丢进ida64中进行反编译进来之后&#xff0c;我们进入main函数&#xff0c;发现里面没什么东西&#xff0c;那就shiftf12搜索字符串&#xff0c;找到关键字符串&#xff0c;双击进入然后再选中该字符串&#xff0c;ctrl…

2025湖北武汉智慧教育装备信息化展/智慧校园展/湖北高博会

2025武汉教育装备展,2025武汉智慧教育展,2025武汉智慧校园展,2025武汉教育信息化展,2025武汉智慧教室展,湖北智慧校园展,湖北智慧教室展,武汉教学设备展,湖北高教会,湖北高博会 2025湖北武汉智慧教育装备信息化展/智慧校园展/湖北高博会 2025第10届武汉国际教育装备及智慧校园…

Micron近期发布了32Gb DDR5 DRAM

Micron Technology近期发布了一项内存技术的重大突破——一款32Gb DDR5 DRAM芯片&#xff0c;这项创新不仅将存储容量翻倍&#xff0c;还显著提升了针对人工智能&#xff08;AI&#xff09;、机器学习&#xff08;ML&#xff09;、高性能计算&#xff08;HPC&#xff09;以及数…

大数据之Zookeeper部署

文章目录 集群规划环境准备集群部署参考资料 集群规划 确定使用Hadoop101、hadoop102和hadoop103三台服务器来构建Zookeeper集群。 hadoop101hadoop102hadoop103zookeeperzookeeperzookeeper 环境准备 安装zookeeper前需要确保下面的环境配置成功&#xff0c;具体可以参考大…

使用echarts绘制中国地图根据不同的省份划分到指定区域里面中

需求&#xff1a;我们在开发过程中会遇到使用中国地图来划分不同区域省份下面的数量统计情况&#xff0c;但是有时候使用Echarts里面地图功能和我们实际业务需求不匹配的&#xff0c;这个时候就需要我们手动自定义进行划分不同区域下面的省份数据。例如大区1下面有哪些省份&…

查询数据库下所有表的数据量

个人思路: 首先把库里Schema下表名拿出来放记事本(EmEditor)里, 用一下正则匹配替换 (\w) → select \1 tableName,count(1) from \1 union all 然后把最后的union all删除掉,替换为order by tableName

Linux 【线程池】【单例模式】【读者写者问题】

&#x1f493;博主CSDN主页:麻辣韭菜&#x1f493;   ⏩专栏分类&#xff1a;Linux初窥门径⏪   &#x1f69a;代码仓库:Linux代码练习&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Linux知识   &#x1f51d; 目录 &#x1f3f3;️‍&#x1f308;前言 …

在安装HDFS过程中遇见Permission denied

HDFS Shell命令权限不足问题解决 问题 想必有同学在实战Shell的时候&#xff0c;遇到了&#xff1a; Permission denied: userroot, accessWRITE, inode"/":hadoop:supergroup:drwxr-xr-x 这种类似的问题。 问题的原因就是没有权限&#xff0c;那么为什么呢&#…

数字化精益生产系统--MRP 需求管理系统

MRP&#xff08;Material Requirements Planning&#xff0c;物料需求计划&#xff09;需求管理系统是一种在制造业中广泛应用的计划工具&#xff0c;旨在通过分析和计划企业生产和库存需求&#xff0c;优化资源利用&#xff0c;提高生产效率。以下是对MRP需求管理系统的功能设…

2.1 tmux和vim

文章目录 前言概述tmuxvim总结 前言 开始学习的时间是 2024.7.6 ,13&#xff1a;47 概述 最好多使用&#xff0c;练成条件反射式的 直接使用终端的工具&#xff0c;可以连接到服务器&#xff0c;不需要使用本地的软件 tmux 这个主要有两个功能&#xff0c;第一个功能是分…

【十三】图解 Spring 核心数据结构:BeanDefinition 其二

图解 Spring 核心数据结构&#xff1a;BeanDefinition 其二 概述 前面写过一篇相关文章作为开篇介绍了一下BeanDefinition&#xff0c;本篇将深入细节来向读者展示BeanDefinition的设计&#xff0c;让我们一起来揭开日常开发中使用的bean的神秘面纱&#xff0c;深入细节透彻理解…

【Pyhton】读取寄存器数据到MySQL数据库

目录 步骤 modsim32软件配置 Navicat for MySQL 代码实现 步骤 安装必要的库&#xff1a;确保安装了pymodbus和pymysql。 配置Modbus连接&#xff1a;设置Modbus从站的IP地址、端口&#xff08;对于TCP&#xff09;或串行通信参数&#xff08;对于RTU&#xff09;。 连接M…

go语言day10 接口interface 类型断言 type关键字

接口&#xff1a; 空接口类型&#xff1a; 要实现一个接口&#xff0c;就要实现该接口中的所有方法。因为空接口中没有方法&#xff0c;所以自然所有类型都实现了空接口。那么就可以使用空接口类型变量去接受所有类型对象。 类比java&#xff0c;有点像Object类型的概念&#x…

文件上传(本地、OSS)

什么是文件上传&#xff1a;将文件上传到服务器。 文件上传-本地存储 前端 <template> <div><!-- 上传文件需要设置表单的提交方式为post&#xff0c;并设置enctype属性、表单项的type属性设置为file --><form action"http://localhost:8080/wedu/…

嵌入式Linux系统编程 — 7.2 进程的环境变量

目录 1 什么是进程的环境变量 2 环境变量的作用 3 应用程序中获取环境变量 3.1 environ全局变量 3.2 获取指定环境变量 getenv 4 添加/删除/修改环境变量 4.1 putenv()函数添加环境变量 4.2 setenv()函数 4.3 unsetenv()函数 1 什么是进程的环境变量 每一个进程都有一…