[数据结构]-玩转八大排序(二)冒泡排序快速排序

news/2024/5/2 7:49:24/文章来源:https://blog.csdn.net/qq_61552595/article/details/126979360

前言

作者小蜗牛向前冲

名言我可以接收失败,但我不能接收放弃

 如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。

目录

一 冒泡排序

二 快速排序 

1 hoare版本实现

2 挖坑法实现

3 前后指针法

三 快速排序的优化

1 三数取中优化key的取值

2 小区间优化

四 非递归实现快速排序

五 快排的特性总结


书接上回!我们前面讲到了八大排序中的插入排序选择排序。选择排序中的堆排序效率是较高的,其中的希尔排序数组接近有序效率也是相当高的;那还有哪些更好的排序吗?有的,本篇介绍的快速排序就比较厉害在讲快速排序之前我们先要实现一下我们的老朋友冒泡排序

其中 冒泡排序快速排序都属于交换排序:

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

一 冒泡排序

冒泡排序的简单思路就是二个数二二比较,将如果前一个数比后一个数大就交换,以此类推就可以完成数组的排序。

 下面我们就用代码来实现一下吧!

//冒泡排序
void BubbleSort(int* a, int n)
{int i = 0;//控制排序的趟数for (i = 0;i < n;i++){int exchange = 0;//完成一次排序for (int j = 0;j < n - 1 - i;j++){if (a[j] > a[j + 1]){swop(&a[j], &a[j + 1]);exchange = 1;}}//数组有序直接跳出if (exchange == 0){break;}}
}

 这里我们测试一下:

 冒泡排序的特性总结:
1. 冒泡排序是一种非常容易理解的排序
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)
4. 稳定性:稳定

二 快速排序 

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


 

 对于快速排序其实是有三种方法的,下面我们一一去实现他。

1 hoare版本实现

hoare大佬不得不说是真的厉害,下面我们就去看看大佬是这么实现快速排序的吧!

首先我们可以先排一个数到指定位置

1 选择一个数key(一般是第一个或者是最后一个)

2 在单趟排序中是要保证小数在key的左边,大数在key的右边。

 下面我们走一下这个思路:这里我们要先让R先走(至于为什么后面会有解答),注意R是找小数,找到就停下来L是找大数,找到也停下来,然后交换R和L位置的数。

 最后当他们相遇的时候就停下来,交换此位置的数和key。

 这里解释一下我们为什么要让R先走,当key在左边的时候。

其实我们是要保证相遇的位置要比key要小,当R先走的情况,R和L相遇的情况无非有二种情况:

L撞R

R撞L

 可以看到,当R先走的时候无论是R撞L还是L撞R都会满足相遇的位置要比key要小。同理,当key在最右边的时候,L就要先出发。

当key换到R和L的相遇位置后,就说明key已经排序好了。

下面我们写个代码实现一下:

// hoare,区间[left,right]
int PartSort1(int* a, int left, int right)
{int keyi = left;while (right > left){//R找小while (right > left && a[right >= a[keyi]]){--right;}//L找大while (right > left && a[left] <= a[keyi]){++left;}//注意这里R和L交换的前提一定要有right > leftif (right > left){swop(&a[left], &a[right]);}}//出来后R和L相遇,与keyi位置的值交换int meeti = left;swop(&a[meeti], &a[keyi]);return meeti;//返回相遇位置的下标
}

这里我们实现了一个key的排序,但要排序好整个数组,我们还要选择出无数个key,在排序key,这就不得不用到的递归实现了。

递归实现选出key的排序

我们知道key的左边都是比他小的数右边都是比他大的数。那么key排序位置就是此位置了。这里时我们只要去排key的左区间的数和右区间的数,同理,我们在这里二个区间继续选key排序,就像展开的二叉树一样

代码实现:

//快速排序
void QuickSort(int* a, int begin,int end)
{//递归结束的标志if (begin >= end)return;int keyi = PartSort1(a, begin, end);//排key的左区间QuickSort(a, begin, keyi - 1);//排key的右区间QuickSort(a, keyi+1,end);
}

这里我们测试一下

 好了这里就hoare大佬写的版本,但是有另些大佬觉的这个方法不容易理解。所以大佬们就想出了另外一个方法挖坑法

2 挖坑法实现

该方法其实和hoare大佬写的方法是非常相似的,就只是做一些改动。

 我们仍然让R找小数,L找大数,在最左边选出key并形成一个坑位,当R找到小数时,停下来,填到左边坑中去,并在此位置形成新的坑位在L走找大数,当找到大数时,停下来,将大数填到右边的坑位中,并形成新的坑位;当L和R相遇后,也即是在一个坑位上,这样我们就将key填到坑中去。

大家可能回想这和hoare大佬写的有什么区别吗?

其实是有的,大家可以对比一下二个版本,排第一个key时各个数据的位置。

hoare版本

 挖坑法

下面我们实现一下吧!

//挖坑法
int  PartSort2(int* a, int left, int right)
{int hole = left;int key = a[left];while (left < right){//R找小数,填到左边坑中while (left < right && a[right] >= key){--right;}a[hole] = a[right];hole = right;//形成新的右边坑位//L找到数。填到右边坑位中while (left < right && a[left] <= key){++left;}a[hole] = a[left];hole = left;//形成新的左边坑位}a[hole] = key;return hole;
}

 看到这里不少小伙伴肯定觉得,这样应该就是最优写法了吧!,但是大佬们还不满意,他们认为还是容易写错,为什么这么说呢?因为我们很容易漏写在R找小和L找到及交换过程中都要满足right>left这个条件;是的!真的很容易漏写,博主就烦了这样的错误,很显然他报错了

 我调试了一下,发现left++都超出数组范围了。

 所以大佬们想了一种更加简单的写法。

3 前后指针法

这种方法我们需要二个指针,prev指向序列的开头,cur指针指向prev的后一个位置,

 那么prev指针和cur指针又在干什么呢?

其实,cur指针在找小,遇到比key小的值将停下来,++prev,交换prev和cur位置上的数据;

而prev是有二种状态的,一是紧紧跟这cur,二是在比key大的前一个数停下来当cur越界后,就将a[prev]的a[keyi]交换。

1 这里我们需要要注意,cur只有遇到比key值小的值才停下来,在观察++prev是否和cur相等,如果相等就没有必要交换了。

2 因为是cur比prev先走,而cur只有遇到比key值小的值才停下来,所以a[perv]的值是一定比key大的。

下面我们写代码实现:

//前后指针法
int PartSort3(int* a, int left, int right)
{int prev = left;int cur = left + 1;int keyi = left;while (cur <= right){//找小if (a[cur] < a[keyi] && ++prev != cur)swop(&a[cur], &a[prev]);++cur;}//cur越界,交换swop(&a[keyi], &a[prev]);return prev;
}

这里我们测试一下快速排序的性能“

//测试性能
void TestOP()
{srand(time(0));const int N = 100000;int* a1 = (int*)malloc(sizeof(int) * N);int* a2 = (int*)malloc(sizeof(int) * N);int* a3 = (int*)malloc(sizeof(int) * N);int* a4 = (int*)malloc(sizeof(int) * N);int* a5 = (int*)malloc(sizeof(int) * N);int* a6 = (int*)malloc(sizeof(int) * N);for (int i = 0; i < N; ++i){a1[i] = rand();a2[i] = a1[i];a3[i] = a1[i];a4[i] = a1[i];a5[i] = a1[i];a6[i] = a1[i];}int begin1 = clock();InsertSort(a1, N);int end1 = clock();int begin2 = clock();ShellSort(a2, N);int end2 = clock();int begin3 = clock();SelectSort(a3, N);int end3 = clock();int begin4 = clock();HeapSort(a4, N);int end4 = clock();int begin5 = clock();QuickSort(a5, 0, N - 1);int end5 = clock();int begin6 = clock();//MergeSort(a6, N);int end6 = clock();printf("InsertSort:%d\n", end1 - begin1);printf("ShellSort:%d\n", end2 - begin2);printf("SelectSort:%d\n", end3 - begin3);printf("HeapSort:%d\n", end4 - begin4);printf("QuickSort:%d\n", end5 - begin5);printf("MergeSort:%d\n", end6 - begin6);free(a1);free(a2);free(a3);free(a4);free(a5);free(a6);
}int main()
{//int a[] = { 9,3,4,1,8,0 };//int n = sizeof(a) / sizeof(a[0]);TestOP();//testheap();/*BubbleTest(a, n);*///QuickTest(a, n);return 0;
}

 

我们可以看到快排是非常优秀的,有人可能会问,这么希尔排序会比快速排序还要优秀,其实不然,因为我们的随机数是用rand函数生成的,当生成随机的范围最大就 RAND_MAX(32767),而我们数组中有10万个数,也就意味这会生成许多相同的数,而且由于我们是用时间戳来作为种子,而当我们调用生成随机数速度没有超过1秒,就会生成许多相同的而且连续存放的随机数。我们说过当我们去排一个相对有序序列时希尔排序的效率是非常高的,而快排就要不断递归下去排序。

 

 

三 快速排序的优化

他们心里可能回想,这快排都这么快了还这么优化呢?

其实不然,当我们用当前思路去排已经排好的数据会这么样呢?我们上面说到排序一个相对有序的序列时会比不上希尔,但更为重要的是当数组数据较大而接近有序的时候,递归的深度会过高,导致栈溢出。那我们还有上面办法去优化吗?

 序列非有序的递归接进二叉树的形式

 序列有序的递归的形式

1 三数取中优化key的取值

为了避免出现序列出现有序或者接近有序情况导致快排的效率低下,其实如果我们的key如果每次都是一个中位数就好了,那么排序的时间复杂度就为O(N*longN),为了达到这个目的,我们用三数取中的方式。每次将a[left],a[mid],a[right]三个数的大小进行比较,找到中为数做为key。

下面代码实现一下:

//三数取中
int GetMidIndex(int* a, int left,int right)
{int mid = (left + right) / 2;//三数二二比较if (a[left] < a[mid]){if (a[mid] < a[right]){return mid;}else if (a[mid] > a[left]){return right;}else{return left;}}else//(a[left] > a[mid]{if (a[mid] > a[right]){return mid;}else if (a[right] > a[left]){return left;}else{return right;}}
}

这里我们用已经排序好a4数组验证一下:

 那快速排序还能优化吗?有的,其实我们知道当排序到最后几层的时候,就要调用大量的递归,我们最后一层相当要调用1/2的递归。倒数第2层要调用1/4的递归,倒数第3层要调用1/8的递归。

2 小区间优化

三数取中虽然进行了一定程度的优化,那么我们能不能减少递归的调用呢?因为过分的递归调用会消耗大量的栈空间而导致栈溢出。如果我们把最后3层改为插入排序,这样因为原先通常快速排序已经相当有序了,效率也不会太低,还能减少大部分的递归调用。

代码实现:

//快速排序
void QuickSort(int* a, int begin,int end)
{//递归结束的标志if (begin >= end)return;//最后3层改为插入排序if (end - begin < 8){InsertSort(a + begin, end - begin - 1);}else{int keyi = PartSort3(a, begin, end);//排key的左区间QuickSort(a, begin, keyi - 1);//排key的右区间QuickSort(a, keyi + 1, end);}
}

测试一下

 这里可以看到当数组有100万个数据的时候栈还是没有溢出,虽然效率低了,但我们扩大了快速排序的使用范围。

四 非递归实现快速排序

虽然我们优化了快速排序让他不那么容易就栈溢出了,但是当数据非常大的有时候栈还是会溢出,因为栈空间大概只有8MB,而堆上的空间有几个G,我们可以将递归改写为非递归。

我们知道递归的思路是按照区间来排的,先是排整个数组,然后是左右区间,在把左区间分为左右区间,把右区间分为左右区间来排依次类推。数组的边界我们可以用left和right。

那么非递归的思路就出来了:这里我们要借助数据结构的栈,首先我们要将数组的左右边界入栈,然后取出栈顶的right和left(取出来的同时我们还要进行pop),这时我们就可以用left和right表示一个去间了;之后我们调用单趟排序进行排序排序完成后我们在此区间的左右区间入栈;重复上述操作,直到栈中的元素为空。

//非递归实现
void QuickSortNonR(int* a, int begin, int end)
{ST st;StackInit(&st);//数组区间入栈StackPush(&st, begin);StackPush(&st, end);while (!StackEmpty(&st)){//出区间int right = StackTop(&st);StackPop(&st);int left = StackTop(&st);StackPop(&st);//单趟排序int keyi = PartSort3(a, left, right);//如果区间中的元素>1//先入右边的区间if (right > keyi + 1){StackPush(&st, keyi+1);StackPush(&st, end);}//在入左边区间if (left < keyi - 1){StackPush(&st, left);StackPush(&st, keyi-1);}}//销毁栈StackDestroy(&st);
}

测试一下:

五 快排的特性总结

快速排序的特性总结:

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(logN)
4. 稳定性:不稳定

这期博客就到这里,下期博客继续和大家分享归并排序和非比较排序

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

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

相关文章

无刷电机驱动板开发,加速无刷时代前进步伐

无刷电机是由电机主体和电机驱动板组成的一种没有电刷和换向器的机电一体化产品。随着控制板的应用场景逐渐丰富&#xff0c;控制板开发技术日益增强&#xff0c;目前无刷电机的发展方向&#xff0c;首先就是对无刷电机驱动板的开发。 无刷电机的优点非常的多&#xff0c;其中最…

Nginx系列之请求处理的11个阶段(下)

上篇博客介绍了PostRead阶段涉及的模块的作用&#xff0c;此篇博客将介绍剩余阶段所涉及的模块作用 Preaccess阶段的limit_conn和limit_req模块 限速&#xff08;rate limiting&#xff09;是NGINX中一个非常有用的特性&#xff0c;可以用它来限制在一段时间内的HTTP请求数量…

慢查询 MySQL 定位优化技巧,从10s优化到300ms

1.如何定位并优化慢查询SQL&#xff1f; 一般有3个思考方向 1.根据慢日志定位慢查询sql 2.使用explain等工具分析sql执行计划 3.修改sql或者尽量让sql走索引 2.如何使用慢查询日志&#xff1f; 先给出步骤&#xff0c;后面说明 有3个步骤 1.开启慢查询日志 首先开启慢查询…

office project【图文详解】

Office Project是一款全面实用的项目管理软件。 Office Project2021正式版来自推出的项目管理软件。最新版本的Microsoft Office Project2021软件拥有最简单的项目管理方法&#xff0c;用户可以根据每个人的工作情况轻松分配工作量和项目执行时间。Project 安装包各个版本 htt…

(附源码)小程序儿童艺术培训机构教育管理小程序 毕业设计 201740

摘 要 本文介绍了使用PHPMySQL数据库和微信应用为儿童艺术学校实现教育应用的过程。本文介绍的教育应用是为儿童艺术学校设计的&#xff0c;这意味着该系统可用于教授儿童艺术和艺术视频。该系统的主要功能是可以将儿童艺术教育的线下方式转变为线上&#xff0c;从教育机构的角…

微信小程序组件介绍

组件view 普通视图区域 类似于html中的div是一个块级元素 .wxml代码 <view class"container1" ><view>温度</view><view>湿度</view> <view>光照强度</view> </view> .wxss代码—— .container1 view{width: 100…

linux 内存管理

用户空间与内核空间 人间还是仙界&#xff1f;聊一聊linux系统的用户空间和内核空间 以32 位Linux系统为例&#xff0c;虚拟地址的大小是4GB (0x0000_0000 ~ 0xffff_ffff)。 Linux 用户空间和 内核空间的大小可以通过设置宏 PAGE_OFFSET 来配置&#xff0c;默认PAGE_OFFSET 0…

Oracle数据库中的包(七)

目录 1.Oracle中包 2.包的创建 &#xff08;1&#xff09;可视化方式创建包 &#xff08;2&#xff09;以命令方式创建包 ①创建包头 ②创建包体 ③删除包 3.包的初始化 4.重载 ①相关概念和注意事项 ②系统内置的包 Oracle学习的相关知识点&#xff08;汇总&#x…

UGeek大咖说 | 直播预告:顺丰高难度可观测性压测实践与应用

本月「UGeek大咖说-大厂可观测」又双叒……来和大家见面了&#xff01;本期大咖说特邀到顺丰科技应用架构高级工程师——李卓做客直播间&#xff0c;用实际案例带我们一起剖析大型复杂系统下可观测性在全链路压测中的落地实践。 往期大咖说我们对可观测性做了很多诠释和分享&a…

2022安徽省赛赛题——B-2任务二:流量分析

有题有环境有解析,要的私我,勿喷! B-2任务二:流量分析 *任务说明:仅能获取Server2的IP地址 1.使用Wireshark查看并分析Server2桌面下的capture.pcapng数据包文件,找出黑客获取到的可成功登录目标服务器Telnet服务的账号密码,并将黑客获取到的账号密码作为Flag值(用户…

开创性的区块链操作系统项目——去中心化簿订单交易所

关于区块链操作系统上的 Web2 和 Web3 先驱系列今天向大家介绍来自Dakai的 Peter、Laszlo 和 Mark 。Web3 开发人员通过他们的去中心化簿订单交易所推进了区块链技术的发展。他们正在使用 Python 和 SQLite 作为数据库引擎来进行开发&#xff0c;他们发现他们可以在区块链操作系…

js与jquery实例-拖动改变列宽和行高

如何通过javascript或者jquery实现改变表格宽度或者行高的功能?今天就把这个功能代码分享给大家,绝对原创哦,代码少而且易懂。先看效果图:html结构:html结构:<!DOCTYPE HTML> <html> <head><meta charset="utf-8"><title>table&…

最适合从事游戏建模这类高薪职业的是这些人,快来看看有你吗?

随着游戏行业的发展&#xff0c;游戏建模受到越来越多的人的关注&#xff0c;那游戏建模的学习适用于什么样的人群呢&#xff1f;今天就来介绍一下吧 01 大学毕业&#xff0c;就业方向不明确 大学期间&#xff0c;本专业知识没有深度掌握&#xff0c;无法从事本专业相关的工作…

Vue3 i18国际化

本文参考了两片文章如下&#xff0c;博文原创&#xff0c;转载附上本博文链接即可 1、基于Vue3.0和ElementPlus开发后台框架(loginbacki18n)_zzzzzzzzzz的博客-CSDN博客_vue3后台框架 &#xff08;这个有点没看懂&#xff09; 2、https://www.jianshu.com/p/fa85595642cd&am…

盘点一个Python网络爬虫实战问题

大家好,我是皮皮。 一、前言 前几天在Python铂金交流群【红色基因代代传】问了一个Python网络爬虫的问题,提问截图如下:代码截图如下:报错截图如下:要么就是原始网页没那么多数据,要么就是你自己取到的数据没那么多,有的有排名,有的没有,可以考虑加个try异常处理。首先…

基于单片机的老人防摔GSM报警

目录 1 跌倒报警器研究现状........................................................................................ 8 2.1单片机的功能及最小系统的电路设计.................................................. 9 内置闪存存储器......................................…

雷鸟乐队 VoxEdit 大赛启动啦,24,500 SAND 奖励等你们来赢取!

是鸟……是飞机……是雷鸟&#xff01; 如果你们选择接受它&#xff0c;那么你们的任务是创造一个受 1960 年代标志性电视剧启发的车辆资产&#xff08;汽车、轮船摩托车等&#xff09;。 不要使用雷鸟的 logo 或对现有的雷鸟作品进行二次创作。 24,500 SAND 将按以下方式分配给…

手机远程控制之scrcpy(一)

有线投屏 无线投屏 屏幕录制 常见问题 错误检查 ERROR: Exception on thread 投屏模糊 scrcpy 是免费开源的投屏软件&#xff0c;支持将安卓手机屏幕投放在 Windows、macOS、GNU/Linux 上&#xff0c;并可直接借助鼠标在投屏窗口中进行交互和录制。 市面上主流的多屏协…

机器人地面站-[QGroundControl源码解析]-[7]-[api]

目录 前言 一.QmlComponentInfo 二.QGCSettings 三.QGCOptions 四.QGCCorePlugin 总结 前言 上篇讲完了Analyize中内容&#xff0c;主要对应界面上AnalyzeTool模块的功能。本篇我们来过api文件夹下的代码。api下的代码主要实现了qgc的核心接口&#xff0c;应用所有的选项…

为什么2022年7月的PMP考试通过率这么低?

2022年 7月考的是新考纲&#xff0c;有50%的敏捷题型&#xff0c;考题相对旧考纲灵活很多&#xff0c;混合型项目内容较多&#xff0c;要是不好好备考&#xff0c;很有可能挂哦&#xff0c;所以 PMI 官方都发布通知&#xff0c;7、8、9 月没考过的考生可以免费重考一次。 但是&…