zoukankan      html  css  js  c++  java
  • 排序算法 —— 基于比较的排序算法稳定性及其汇总

    前言

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

    排序比较图

    排序比较图

    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技术小栈

  • 相关阅读:
    Python中所有的关键字
    关于selenium的8种元素定位
    对提示框的操作
    selenium+webservice进行百度登录
    MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled...报错解决
    Vue中使用echarts
    npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142解决方法
    插入排序
    冒泡排序优化
    roject 'org.springframework.boot:spring-boot-starter-parent:XXX' not found 解决
  • 原文地址:https://www.cnblogs.com/mmimo/p/15521791.html
Copyright © 2011-2022 走看看