• 排序算法 —— 基于比较的排序算法稳定性及其汇总


    前言

    我们在之前的排序算法文章中,介绍了六种基于比较的排序算法:选择排序、冒泡排序、插入排序、归并排序、快速排序和堆排序,现在我们来总结一下每个算法对应的时间复杂度、空间复杂度以及稳定性

    排序比较图

    排序比较图

    1. 稳定性

    稳定性的意思就是对于两个值相等的元素,在排完序后,它们的相对位置没有发生改变,这样的算法就是稳定的

    我们先说说为什么要研究算法的稳定性:对于简单类型的比较,譬如比较数组中的数,那么每个数值都是等效的,稳定性就没有什么用;不过对于复杂类型,稳定性就很有用了。举个栗子:比如一群学生,他们有两个属性,一个是成绩,一个是班级,我想先按照他们的成绩进行降序排序,之后再根据班级进行升序排序;那么排完成绩后,我们再根据班级进行排序时,如果排序算法是不稳定的,会导致之前排好的成绩可能会因为班级排序而打乱;而对于稳定的排序算法,同一个班级的数据的相对位置就不会发生改变,也就不会影响到之前已经排好的成绩顺序

    讲完了稳定性的用处后,我们再来逐个分析一下上述六大排序的稳定性

    1. 对于选择排序,它是不稳定的排序,举例如下:

      当找到数组中的最小值 2,并与数组第一个元素进行交换后,两个数字 3 的相对顺序就发生了改变,因此选择排序是不稳定的排序

    选择排序稳定性举例

    1. 对于冒泡排序,它是稳定的排序,因为当冒泡的元素只有遇到比它小的元素时,它才交换位置;遇到相同元素时并不交换位置,所以不会改变相同元素之间的相对位置

    2. 对于插入排序,它是稳定的排序,因为插入的元素只有遇到比它大的元素时,它才会继续往前比较,直到找到合适的插入位置;遇到相同元素时,它就停止了比较,所以不会改变相同元素之间的相对位置

    3. 对于归并排序,它是稳定的排序,我们回想一下归并排序里的归并过程,当左右指针所指的元素相同时,左指针所指的数先放入数组中,所以不会改变它们的相对位置,所以归并排序也是稳定的排序(之前我们讲归并排序的应用时提到了小和问题,小和问题中当左右指针所指的元素相同时,是右指针所指的数先放入数组,所以小和问题中的排序并不是稳定的排序)

    4. 对于快速排序,它是不稳定的排序,举例如下:

      如下所示的排序过程中,若 5 为基准元素,左边为小于 5 的元素,右边为大于 5 的元素,那么当指针 i 指向第三个元素时,因为 6 比基准值 5 大,因此 6 与 5 要交换位置,两个数字 6 的相对位置就发生了改变,因此快速排序是不稳定的排序

      快速排序稳定性举例

    5. 对于堆排序,它是不稳定的排序,因为堆排序使用了堆这个数据结构,它在比较的时候只和自己的父结点或孩子结点进行比较,压根不关心稳定性的问题,不过为了理解,我们还是举个栗子,沿用之前介绍堆排序稳定性使用的栗子

    堆排序稳定性示例 1

    对于这样的大根堆,如果我们使堆上浮,去掉里面最大的元素,那么最后一个元素 3 就会跑到根结点的位置,于是两个 3 的相对位置就发生了变化,因此堆排序是不稳定的排序

    堆排序稳定性示例 2

    根据以上的分析,我们得出一个结论:

    不具备稳定性的排序有选择排序、快速排序和堆排序;它们的特点是元素交换时会进行跳跃,这种跳跃交换就很可能导致相对位置发生变化

    有稳定性的排序有冒泡排序、插入排序、归并排序(tips: 计数排序和基数排序这两个不基于比较的排序也是稳定的排序)

    2. 时间复杂度和空间复杂度

    我们现在来分析一下他们的时间复杂度和空间复杂度

    1. 选择排序算是里面最 low 的一个了,它的时间复杂度为 O(N²)这样高的级别,却还不能保证稳定性

    2. 现在我们来看看下面这三个排序

      image-20211107200839706

    • 对于归并排序、快排以及堆排序,它们的时间复杂度均为 O(NlogN),但是经实验表明,从常数时间来讲,快排的速度是最快的,所以我们优先选择快排(面试和笔试中快排应该也是最常考的)
    • 如果你遇到空间不够用,或者不能使用额外空间的情况,那就可以用堆排序;如果你需要稳定的排序算法,那就可以使用归并排序
    1. 目前还没有找到时间复杂度小于 O(NlogN) 的排序算法

    2. 目前还没有发现时间复杂度为 O(NlogN),空间复杂度在 O(N) 以下,同时又有稳定性的算法

    3. 所以我们选择排序算法时,不可能时间复杂度、空间复杂度、稳定性三者好处都占完,目前还没找到这样的算法,我们只能根据我们的实际情况选择要么最快、要么额外空间使用最少、要么稳定的算法

    4. 工程上对排序的改进:我们可能在某些快排的代码中,看到这一段代码

      if(l > r - 60) {
          // 在 arr[l...r] 插入排序
          // O(N²) 小样本量的时候跑得快
          return;
      }
      

      这正是一种综合排序,充分利用了 O(NlogN) 和 O(N²) 各自的优势,对于小样本而言,虽然时间复杂度是 O(N²),但是它此时是常数时间,瓶颈并不明显;这样它就能在大样本中运用快排的时间复杂度优势,小样本中利用插入排序常数时间低的优势

    欢迎来逛逛我的博客 mmimo技术小栈

  • 相关阅读:
    团队二阶段冲刺个人工作总结9
    团队二阶段冲刺个人工作总结8
    团队二阶段冲刺个人工作总结7
    团队二阶段冲刺个人工作总结6
    PSP总结报告
    第十三周例行报告
    对团队成员公开感谢博客
    附加作业 软件工程原则的应用实例分析
    第十二周例行报告
    第十一周例行报告
  • 原文地址:https://www.cnblogs.com/mmimo/p/15521791.html
Copyright © 2020-2023  润新知