【数据结构与算法】数据结构初阶:排序内容加餐(一)——快速排序:三路划分、自省排序


🔥个人主页:艾莉丝努力练剑

❄专栏传送门:《C语言》、《数据结构与算法》、C语言刷题12天IO强训、LeetCode代码强化刷题

🍉学习方向:C/C++方向

⭐️人生格言:为天地立心,为生民立命,为往圣继绝学,为万世开太平


 


这个用来测试代码的对比排序性能的代码博主还是放在下面,大家可以对比一下各种排序算法的运行时间,从而对不同排序方法的时间复杂度有更加直观地认识:

代码演示:

//测试排序的性能对比  
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);int* a7 = (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];a7[i] = a1[i];}//begin和end的时间差就是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();int begin7 = clock();BubbleSort(a7, N);int end7 = 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);printf("BubbleSort:%d\n", end7 - begin7);free(a1);free(a2);free(a3);free(a4);free(a5);free(a6);free(a7);
}

前言:本篇文章,我们继续来介绍排序相关的知识点,在初阶的数据结构与算法阶段,我们把知识点分成三部分,复杂度作为第一部分,顺序表和链表、栈和队列、二叉树为第二部分,排序为第二部分,我们之前已经介绍完了第一部分:算法复杂度和第二部分:顺序表和链表、栈和队列、二叉树。本文我们继续开始学习第三部分中的排序的内容啦。 


目录

正文

一、三路划分

1、三路划分的概念

2、思想

3、代码实现

(1)写法1

(2)写法2 

(3)写法3

二、自省排序(内观排序)

1、找基准值出现问题

2、代码实现 

3、完整代码

结尾


正文

一、三路划分

1、三路划分的概念

使用三路划分可以提升当数组中,有大量相同的值时,排序的时间效率,三路划分的核心思想有点类似hoare版本的left、right左右指针lomuto双指针法的前后指针的结合(这里的hoare版本的左右指针和lomuto双指针的前后指针博主在之前的文章中已经介绍过了)。

核心思想:就是把数组中的数据分为三段:比key小的值、跟key相等的值以及比key大的值,因此叫做三路划分算法。

2、思想

三路划分实现的思想:

1. key默认取left位置的值;

2. left指向区间最左边,right指向区间最后边,cur指向left+1位置;

3. cur遇到比key小的值后跟left位置交换,换到左边,left++,cur++;

4. cur遇到比key大的值后跟right位置交换,换到右边,right--;

5. cur遇到跟key相等的值之后,cur++;

6. 直到 cur > right 结束——

3、代码实现

代码演示:

(1)写法1
//三路划分
KeyWayIndex PartSort3Way(int* arr, int left, int right)
{int key = arr[left];//left和right指向就是跟key相等的区间//[begin,left-1]  [left,right]  [right+1,end]int cur = left + 1;while (cur <= right){//1、cur遇到比key小,小的换到左边,同时把key换到中间位置//2、cur遇到比key大,大的换到右边if (arr[cur] < key){Swap(&arr[cur], &arr[left]);++left;++cur;}else if (arr[cur] > key){Swap(&arr[cur], &arr[right]);//cur不能动--right;}else{++cur;}}KeyWayIndex kwi;kwi.leftkeyi = left;kwi.rightkeyi = right;return kwi;
}

不理解这个写法也没关系,博主自己测试的时候用的是这个写法——

(2)写法2 
int _QuickSort3Way(int* arr, int left, int right)
{return;
}//三路划分实现
void QuickSort3Way(int* arr, int left, int right)
{if (left >= right){return;}//随机数取法srand((unsigned int)time(NULL));int randi = left + rand() % (right - left + 1);Swap(&arr[left], &arr[randi]);//三数取中int midi = _QuickSort3Way(arr, left, right);//三路划分int key = arr[left];int cur = left + 1;int begin = left;int end = right;while (cur <= right){//1、cur遇到比key小,小的换到左边,同时把key换到中间位置//2、cur遇到比key大,大的换到右边if (arr[cur] < key){Swap(&arr[cur], &arr[left]);++left;++cur;}else if (arr[cur] > key){Swap(&arr[cur], &arr[right]);//cur不能动--right;}else{++cur;}}//三路划分 [begin,left-1]  [left,right]  [right+1,end]QuickSort3Way(arr, begin, left - 1);QuickSort3Way(arr, right + 1, end);
}

运行一下,确实能跑出来—— 

(3)写法3

此外,还可以采用这种写法——

void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}
void QuickSort(int* arr, int left, int right)
{if (left >= right)return;int begin = left;int end = right;//随机选keyint randi = left + (rand() % (right - left + 1));Swap(&arr[left], &arr[randi]);//三路划分//left和right指向就是跟key相等的区间// [begin, left-1] [left, right] right+1, end]int key = arr[left];int cur = left + 1;while (cur <= right){//1、cur遇到比key小,小的换到左边,同时把key换到中间位置//2、cur遇到比key大,大的换到右边if (arr[cur] < key){Swap(&arr[cur], &arr[left]);++left;++cur;}else if (arr[cur] > key){Swap(&arr[cur], &arr[right]);--right;}else{++cur;}}// [begin, left-1] [left, right] right+1, end]QuickSort(arr, begin, left - 1);QuickSort(arr, right + 1, end);
}
int* sortArray(int* nums, int numsSize, int* returnSize) {srand(time(0));QuickSort(nums, 0, numsSize - 1);*returnSize = numsSize;return nums;
}

时间复杂度:O(n*logn)

二、自省排序(内观排序)

自省排序(Introsort)是一种混合排序算法,它结合了快速排序(Quicksort)、堆排序(Heapsort)和插入排序(Insertion sort)的优点。其核心思想是利用快速排序的高效性,同时在递归深度超过一定限制时切换到堆排序以避免最坏情况的发生,从而保证整体性能。

introsort的快排排序OJ代码

——C++库提出来的

1、找基准值出现问题

这里的introsortintrospective(自省的) sort采用了缩写,他的名字其实表达了他的实现思路,他的思路就是进行自我侦测和反省,快速排序递归深度太深(sgi stl使用的是深度为2倍排序元素数量的对数值)就说明在这种数据序列下,选key出现了问题,性能在快速退化,那么就不要再进行快排分割递归了,改换为堆排序进行排序。

2、代码实现 

代码演示:

//自省排序(内观排序)
void IntroSort(int* arr, int left, int right, int depth, int defaultDepth)
{if (left >= right)return;//数组大小小于16的小数组,换为插入排序,简单递归次数if (right - left + 1 < 16){InsertSort(arr + left, right - left + 1);return;}//当深度超过2*logn,则改用堆排序if (depth > defaultDepth){HeapSort(arr + left, right - left + 1);return;}depth++;int begin = left;int end = right;//随机选keyint randi = left + (rand() % (right - left));Swap(&arr[left], &arr[right]);int prev = left;int cur = prev + 1;int keyi = left;while (cur <= right){if (arr[cur] < arr[keyi] && ++prev != cur){Swap(&arr[prev], &arr[cur]);}++cur;}Swap(&arr[prev], &arr[keyi]);keyi = prev;//[begin,keyi-1]  keyi  [keyi+1,end]IntroSort(arr, begin, keyi - 1, depth, defaultDepth);
}

时间复杂度:O(n*logn)

3、完整代码

代码演示:

#define  _CRT_SECURE_NO_WARNINGS  1/*** Note: The returned array must be malloced, assume caller calls free().*/
void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}
void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){//选出左右孩子中大的那⼀个if (child + 1 < n && a[child + 1] > a[child]){++child;}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}
void HeapSort(int* a, int n)
{//建堆--向下调整建堆-- O(N)for (int i = (n - 1 - 1) / 2; i >= 0; --i){AdjustDown(a, n, i);}// 自己先实现—— O(N * logN)int end = n - 1;while (end > 0){Swap(&a[end], &a[0]);AdjustDown(a, end, 0);--end;}
}
void InsertSort(int* a, int n)
{for (int i = 1; i < n; i++){int end = i - 1;int tmp = a[i];// 将tmp插入到[0, end]区间中,保持有序while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];--end;}else{break;}}a[end + 1] = tmp;}
}
void IntroSort(int* a, int left, int right, int depth, int defaultDepth)
{if (left >= right)return;//数组长度小于16的小数组,换为插入排序,简单递归次数if (right - left + 1 < 16){InsertSort(a + left, right - left + 1);return;}//当深度超过2 * logN时改用堆排序if (depth > defaultDepth){HeapSort(a + left, right - left + 1);return;}depth++;int begin = left;int end = right;//随机选keyint randi = left + (rand() % (right - left + 1));Swap(&a[left], &a[randi]);int prev = left;int cur = prev + 1;int keyi = left;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[prev], &a[cur]);}++cur;}Swap(&a[prev], &a[keyi]);keyi = prev;// [begin, keyi-1] keyi [keyi+1, end]IntroSort(a, begin, keyi - 1, depth, defaultDepth);IntroSort(a, keyi + 1, end, depth, defaultDepth);
}
void QuickSort(int* a, int left, int right)
{int depth = 0;int logn = 0;int N = right - left + 1;for (int i = 1; i < N; i *= 2){logn++;}//introspective sort -- 自省排序IntroSort(a, left, right, depth, logn * 2);
}
int* sortArray(int* nums, int numsSize, int* returnSize) 
{srand(time(0));QuickSort(nums, 0, numsSize - 1);*returnSize = numsSize;return nums;
}

结尾

往期回顾:

【数据结构与算法】数据结构初阶:详解排序(四)——非比较排序:计数排序(鸽巢原理)——对哈希直接定址法的变形应用,排序算法复杂度及稳定性分析

本期内容需要回顾的C语言知识如下面的截图中所示(指针博主写了6篇,列出来有水字数嫌疑了,就只放指针第六篇的网址,博主在指针(六)把指针部分的前五篇的网址都放在【往期回顾】了,点击【传送门】就可以看了)。

大家如果对前面部分的知识点印象不深,可以去上一篇文章的结尾部分看看,博主把需要回顾的知识点相关的博客的链接都放在上一篇文章了,上一篇文章的链接博主放在下面了:

【数据结构与算法】数据结构初阶:详解顺序表和链表(三)——单链表(上)

结语:本篇文章到这里就结束了,对数据结构的排序知识感兴趣的友友们可以在评论区留言,博主创作时可能存在笔误,或者知识点不严谨的地方,大家多担待,如果大家在阅读的时候发现了行文有什么错误欢迎在评论区斧正,再次感谢友友们的关注和支持!

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

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

相关文章

MySqL(加餐)

范式第一范式数据库表的每一列都是不可分割的原子数据项&#xff0c;而不能是集合&#xff0c;数组&#xff0c;对象等非原子数据。在关系型数据库的设计中&#xff0c;满足第一范式是对关系模式的基本要求。不满足第一范式的数据库就不能被称为关系数据库。第一范式实际上只要…

【redis】基于工业界技术分享的内容总结

Redis 实践指南与核心概念 一、Java 中常用的 Redis 使用场景与实践 缓存&#xff08;Caching&#xff09; 场景&#xff1a;热点数据、频繁访问的数据&#xff0c;如商品详情、用户信息。通过缓存减少数据库压力&#xff0c;提高系统响应速度。 工业界实践&#xff1a; 淘宝…

服务端之nestJS常用异常类及封装自定义响应模块

MENU前言常用异常类&#xff08;由nestjs/common提供&#xff09;示例自定义异常&#xff08;可选&#xff09;自定义响应模块前言 在NestJS中&#xff0c;nestjs/common提供了大量的内置异常类&#xff0c;主要用于在控制器、服务等层抛出特定的HTTP错误响应。 常用异常类&…

数据链路层、NAT、代理服务、内网穿透

目录 一. 以太网 以太网帧格式 二. MAC地址 三. MTU 四. ARP协议 五. NAT NAPT 六. 代理服务器 正向代理 反向代理 七. 内网穿透 八. 内网打洞 一. 以太网 • "以太网" 不是一种具体的网络, 而是一种技术标准; 既包含了数据链路层的内 容, 也包含了一些物理层…

Rust在CentOS 6上的移植

Rust已不支持Cent OS 6 rhel是Redhat 发布的Red Hat Enterprise Linux的简称&#xff0c;使用rhel源代码编译的CentOS&#xff0c;最新的版本是CentOS 7&#xff0c;于2024年停止支持。而更古老的CentOS 6&#xff0c;则在2020年就已经结束了。 而面对如此老旧的系统&#xf…

C++音视频开发:基础面试题

音视频领域技术门槛高&#xff0c;学习资料稀缺&#xff0c;体系化书籍和开发工具有限&#xff0c;新手入门困难。音视频开发涉及众多任务&#xff1a;音频&#xff08;采集、编解码、降噪等&#xff09;、视频&#xff08;采集、编解码、图像处理&#xff09;、实时传输&#…

C++刷题 - 7.27

贪心算法的详细逻辑这个问题的最优解可以用 贪心算法 在 O(N) 时间 内解决。它的核心思想是&#xff1a;每次操作尽可能覆盖最长的连续非零区间&#xff0c;并通过数学分析发现&#xff1a;最小操作次数等于所有“上升台阶”的高度差之和。1. 直观理解假设 steps [1, 2, 3, 2,…

音频3A处理简介之AGC(自动增益控制)

在音频通话和视频会议中&#xff0c;音频自动增益控制AGC模块的主要作用&#xff1a;• 稳定音频信号的输出电平。无论麦克风采集信号的强弱&#xff08;如用户离麦克风远近程度不同&#xff09;&#xff0c;尽可能保证音频采集模块的输出音量保持相对一致&#xff0c;不会偏大…

web前端打包apk包

我用的是HBuilder工具,可视化更便捷&#xff0c;目前我这操作的apk包是不需要上架的&#xff0c;所以跟实际需要上架的可能还有些出入 首先先新建个项目&#xff0c;选择5App模式 把目前需要打包的内容上传到服务器&#xff0c;我们以嵌套的形式进行打包&#xff0c;找到index.…

Ansible提权sudo后执行报错

1.问题 配置了sudo提权信息后&#xff0c;执行ansible-play报错&#xff0c;报错信息如下&#xff1a;2.原因 sudo没有执行**/bin/sh的权限&#xff0c;而ansible脚本中依赖/bin/sh**&#xff0c;所以报错了&#xff1a; 查看日志sudo tail -f /var/log/secure3.解决方式 修改*…

.NET报表控件ActiveReports发布v19.0——正式兼容 .NET 9

ActiveReports 是一款专注于 .NET 和 .NET Core 平台的报表控件。通过拖拽式报表设计器&#xff0c;可以快速地设计 Excel表格、Word文档、图表、数据过滤、数据钻取、精准套打等类型报表&#xff0c;全面满足 WinForm、ASP.NET、ASP.NET MVC、WPF 平台中各种报表的开发需要。同…

SCI论文选词炼句

标准句子不能啰嗦&#xff1b;词不能有问题&#xff0c;得是SCI中经常出现的&#xff0c;符合上下文的。SCI论文中常出现的摸棱两可的词单词涵义例子Architecture指 整体系统设计方案&#xff0c;如网络层次结构、模块组合、激活函数选择等深度学习模型架构Structure更泛泛&…

Qt deleteLater 延迟删除原理

deleteLater 调用 事件发送 void QObject::deleteLater() {QCoreApplication::postEvent(this, new QDeferredDeleteEvent()); }首先该对象继承QObject调用deleteLater&#xff0c; 内部会发送删除事件QCoreApplication::postEvent(this, new QDeferredDeleteEvent()) 到事件循…

TypeScript SDK 升级:通过 Upload Relay 赋能更多应用

自 3 月主网上线以来&#xff0c;Walrus 开发者社区持续展现出强劲的发展势头&#xff1a; 当前 Walrus 已存储超 758 TB 数据&#xff0c;为数百个项目提供支持。在 2025 年 6 月举办的 Sui Overflow 黑客松上&#xff0c;Walrus 成为最受欢迎的数据层。该赛事共收到 599 个项…

C#线程同步(二)锁

目录 1.lock 2.Monitor 3.锁的其它要注意的问题 3.1同步对象的选择 3.2什么时候该上锁 3.3锁和原子性 3.4嵌套锁 3.5 死锁 3.6 性能 4.Mutex 5.Semaphore 1.lock 让我们先看一段代码&#xff1a; class ThreadUnsafe {static int _val1 1, _val2 1;static void G…

鸿蒙智能居家养老系统构思(续二)—— 适老化烹饪中心详细构思

一、背景在“写给华为鸿蒙智家 —— 智能居家养老系统构思”一文中&#xff0c;结合对居家养老的理解及个人体验&#xff0c;提出了基于鸿蒙OS实现居家养老系统的粗略构思。其中包含“吃得好”。当老人到了不能随性外出活动、只能在家消耗时光时&#xff0c;除了一些看看电视、…

高斯透镜公式(调整镜头与感光元件之间的距离时,使得不同距离的物体在感光元件上形成清晰的影像)

当使用定焦镜头时&#xff0c;仍然可以调整镜头与感光元件&#xff08;或胶片&#xff09;之间的距离时&#xff0c;使得不同距离的物体在感光元件上形成清晰的影像。对此可以用高斯透镜公式进行解释&#xff1a; 一、透镜成像的基本原理 在光学中&#xff0c;一个基本的公式是…

预过滤环境光贴图制作教程:第三阶段 - GGX 分布预过滤

核心目标 GGX 分布是 PBR 中模拟粗糙表面高光反射的主流模型,其核心是通过统计分布描述微表面的朝向概率。本阶段的目标是: 基于第一阶段生成的环境图集,预计算 6 个级别的 GGX 过滤结果(对应不同粗糙度); 使用蒙特卡洛采样(Monte Carlo Sampling)加速 GGX 卷积计算;…

Spring框架与AutoCAD结合应用

什么是AutoCAD? AutoCAD简介 AutoCAD是由美国Autodesk公司开发的计算机辅助设计(CAD)软件,广泛应用于建筑、工程、制造、产品设计等领域。它支持2D绘图和3D建模,提供精确的图形工具和自动化功能,帮助用户高效创建技术图纸和模型。 主要功能 2D绘图:提供直线、圆弧、多…

Java 学习笔记:常用类、String 与日期时间处理

作为一名名 Java 初学者&#xff0c;最近在学习过程中整理了一些关于常用类、String 类以及日期时间处理的知识点。这些内容是 Java 基础中的重点&#xff0c;也是日常编程练习中频繁用到的工具&#xff0c;掌握它们能让我们在写代码时更加得心应手。今天把这些笔记分享出来&am…