深度剖析“八大排序”(下)- 交换排序 | 快速排序 & 优化 | 非比较排序 _ 探寻一些不为人知的细节

💛 前情提要💛
本章节是数据结构的八大排序(下)的相关知识~
接下来我们即将进入一个全新的空间,对代码有一个全新的视角~
以下的内容一定会让你对数据结构有一个颠覆性的认识哦!!!
❗以下内容以C语言的方式实现,对于数据结构来说最重要的是思想哦❗
以下内容干货满满,跟上步伐吧~
💡本章重点
- 深入了解与实现 - 交换排序
- 深入了解与实现 - 归并排序
🍞一.交换排序
💡基本思想:
- 所谓 - 交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置
- 交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动 
👆简单来说:
- 就是通过依次的比较大小,从而将 - 大值(或- 小值)往后挪动,而- 小值(或- 大值)往前挪动,最终达到排序的效果
而对于交换排序类型最常见的有两种排序算法:
- 1️⃣冒泡排序 
- 2️⃣快速排序 
👉接下来就让我们来深入了解这两种排序算法吧~
🥐Ⅰ.冒泡排序
💡算法思想:
- 1️⃣每一次遍历数组,通过前后比较的方式,让每一趟排序下的 - 最大值都到正确的排序位置上
- 2️⃣反复如此,即可完全排序 
❗特别注意:
- 冒泡排序的本质:可以理解为一个 - 双向选择的过程
- 每一趟一边选出最大值,并将最大值放到最后的位置上 
- 也一边将最小值在每一趟下不断往前移动,直至到达完全排序的位置上 
- 针对特殊情况如:已经是 - 有序(或- 中途有序)的情况下,我们一般对冒泡排序采取优化措施 - 加一个有序- 标识符
- 这样就可以在 一旦有序的情况下,跳出排序进程,减少执行次数,降低时间复杂度 
✊动图示例:
👉代码实现:
🔥排序特性:
- 时间复杂度: 
- 最坏的情况下: 
- 最好的情况下: 
- 稳定性:稳定 
- 冒泡排序和插入排序相比之下: 
- 在已经有序的情况下,它们的时间复杂度一样 
- 在接近有序的情况下,插入排序优于冒泡排序【因为插入排序对于接近有序的情况适应性更强】 
🥐Ⅱ.快速排序
💡算法思想:
- 1️⃣任取待排序元素序列中的某元素作为 - 基准值,按照该排序码将待排序集合分割成两子序列:- 左子序列、- 右子序列
- 2️⃣ - 左子序列中所有元素均小于- 基准值
- 3️⃣ - 右子序列中所有元素均大于- 基准值
- 4️⃣然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止 
👆快排常见版本:
- Hoare 版本(左右指针法) 
- 挖坑法 
- 前后指针版本 
✨接下来让我们逐一讲解上述三个版本吧~
🧇1.Hoare 版本(左右指针法)
➡️算法思想:
- 1️⃣在未排序序列中选出一个值作为 - key(基准值)【一般选择最左边的值 or 最右边的值】
- 2️⃣对于未排序序列的每一趟排序后,都要保证: 
- key值放到正确的位置上
- key值左边的序列的值都要小于- key
- key值右边的序列的值都要大于- key
即需要对于每趟排序都有两个记录下标的标识符:【让与key位置相对的位置标识符先走】
- left:从未排序序列最左边开始出发,负责找比- key大的值,一旦找到就停下来
- right:从未排序序列最右边开始出发,负责找比- key小的值,一旦找到就停下来
- 然后将 - left与- right下标所对应的值进行交换 【当- left与- right相遇的时候,再将- key与- left交换,达到 2️⃣中的要求】
- 3️⃣对未排序序列重复上述的操作,直至排序完全 
⬆️不难发现:
- 通过上述的算法拆分,我们可以利用递归帮助完成 - Hoare版本的快排【也就是采用了- 分治的算法思想】
✊动图示例:
👉代码实现:
❓为什么要让⌈right⌋先走呢
- 因为⌈right⌋是与 - key位置相对的位置标识符【即此时的- key是在最左边,所以与此位置相对的位置就是最右边,即⌈right⌋】
- 之所以有上述要求,是因为这样可以保证直至⌈left⌋与⌈right⌋相遇时,此时下标所对应的数字仍小于 - key所对应的数字
- 那再与 - key交换后,也可以依然保证- key左边的序列的值都小于- key,- key右边的序列的值都大于- key
🧇2.挖坑法
➡️算法思想:
- 与 - Hoare版本的快速排序算法思想基本一致,都是利用- 递归(即- 分治的算法思想)去实现排序
但不同点在于:
- 挖坑法实则是⌈right⌋和⌈left⌋这两个下标标识符只要遇到满足条件的(即找比- key大 or 小的值),就直接放到合适的位置上去【即一边找、一边放】
- 而 - Hoare版本是利用两个下标标识符去记录符合条件的位置,最后再统一交换位置
✊动图示例:
❗特别注意:
- 我们在操作中并不是真正的形成 - 坑位,只是在逻辑操作上想象成- 坑位即可【即实际中- 坑位还是放着原来的元素,只是我们操作的时候先将原来的元素保存起来,然后直接将要放置到- 坑位上的元素去覆盖原来的元素即可】
- 在⌈left⌋与⌈right⌋最后相遇的位置一定是一个 - 坑位,刚好给- key放置
👉代码实现:
🧇3.前后指针法
➡️算法思想:
- 有两个指针: - cur、- prev【- cur的位置在- prev的下一个位置】
- 期间通过 - cur去遍历数组,通过一个一个元素与- key值比较大小
- 若发现比 - key值小的,就让- prev++(即走到下一个位置),再与- cur此时对应的元素进行交换
- 借助 - 递归(即- 分治算法思想)重复上述的步骤,即可实现完全排序
✨简单来说:
- prev与一开始- key所在的位置之间的间隔区间:实则是在维护一段元素都比- key值小的区域【即最终要达到- key的左边的序列的值都要比- key值小的效果】
- prev++与- cur之间的间隔区间:实则是在维护一段元素都比- key值大的区域【即最终要达到- key的右边的序列的值都要比- key值大的效果】
- 而不断让 - cur去找比- key小的值并与- prev++交换的目的:就是为了找到还在序列后面比- key小的元素,并将其交换到- prev与一开始- key所在位置的这段比- key小的序列内,最终保证- key的左子序列的值都比- key小,右子序列都比- key大
✊动图示例:
👉代码实现:
🔥快速排序的优化
💡快速排序的优化两种方式:
- 三数取中 
- 小区间优化排序 
1️⃣三数取中
⭐我们先来看看第一种优化方式:三数取中
- 本质:这是针对 - key选值的优化,避免一开始- key可能选取最大值 or 最小值的情况【所以可以将此优化方式放在排序最开始- key选值的部分】
- 运用了 - 三数取中即可通过一开始就比较序列中的- 头、- 中、- 尾的三个数字的大小,选出大小为中间值的作为- key
❗特别注意:
- 我们依然让 - key在序列最左边的位置,这样可以方便排序
- 所以我们需要再 - 三数取中的优化后,将中间值所在的位置换去序列的最左边的位置
👉代码实现:
2️⃣小区间优化排序
⭐我们先来看看第二种优化方式:小区间优化排序
- 本质:优化排序的时间复杂度,减少不必要的调用递归次数 
- 即在执行第一次的排序递归操作后,已然分为两个待排序区间 - 左子序列、- 右子序列,若这两个序列区间都为- 小区间,便可以直接对- 小区间进行排序,无须再继续- 快速排序的递归操作
❗特别注意:
- 因为是 - 小区间,所以对于有限个数的区间进行排序,我们优先选择- 直接插入排序【此排序的适应性强】
👉代码实现:
🥯Ⅲ.总结
🔥快速排序特性:
- 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序 
- 时间复杂度: 
- 最好的情况下: 
- 最坏的情况下: 
- 空间复杂度:~ 
- 稳定性: 不稳定 
➡️以上就是交换的相关内容啦,相信大家对交换排序有不一样的看法了吧🧡
🍞二.归并排序
💡基本思想:
- 归并排序(MERGE-SORT) 是建立在归并操作上的一种有效的排序算法,该算法是采用 - 分治法(Divide andConquer) 的一个非常典型的应用。
- 将已有序的子序列合并,得到完全有序的序列: 
- 即先使每个子序列有序 
- 再使子序列段间有序 
- 若将两个有序表合并成一个有序表,称为二路归并 
👆简单来说:
- 若将快速排序比喻为二叉树的 - 前序遍历:
- 先找 - key,不断往下划分区间,并对区间内逐渐排序,直至排序完全
- 即在排序完全的同时,一棵树也相当于构建(遍历)好了 
- 则归并排序可以看作为二叉树的 - 后序遍历:
- 不断的划分区间(直至这个区间内如果是有序的,就可以停止划分),然后借助一个额外的空间对区间内元素进行排序,再 - 归并到原来区间上完成排序,直至排序完全
- 即不断划分区间相当于到达一棵树的最底层,再进行 - 归并排序时相当于将这棵树从最后一层开始遍历上去
✊动图示例:
👉代码实现:
💡排序特性:
- 归并的缺点在于需要的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题 
- 时间复杂度: 
- 空间复杂度: 
- 稳定性:稳定 
➡️以上就是归并排序的相关内容啦,相信大家对归并排序有不一样的看法了吧❤️
🍞三.非比较排序
💡基本思想:
- 计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用 
👆简单来说:
- 借助额外的空间,利用额外空间的下标对应原数组的元素数字,统计相同元素出现次数【一般分为: - 绝对映射、- 相对映射两种统计方法】
- 根据统计的结果将序列回收到原来的序列中 
❗特别注意:
- 待排序的数字的范围需要比较集中才适合用 - 计数排序
- 否则不推荐使用,因为会浪费大量的空间 
✊动图示例:
👉代码实现:
💡排序特性:
- 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限 
- 时间复杂度: 
- 空间复杂度: 
- 稳定性:稳定 
➡️以上就是非比较排序的相关内容啦,相信大家对非比较排序有不一样的看法了吧💛
🫓总结
综上,我们基本了解了数据结构中的“排序(下)”的知识啦~
恭喜你的内功又双叒叕得到了提高!!!
感谢你们的阅读
后续还会继续更新,欢迎持续关注哟~
版权声明: 本文为 InfoQ 作者【Dream-Y.ocean】的原创文章。
原文链接:【http://xie.infoq.cn/article/168ce54547fb0a62473308d8b】。未经作者许可,禁止转载。

 
  
  
  
  
  
  
 









 
    
评论