前端不懂算法(二)--插入排序,选择排序

审核大哥,这篇文章是介绍前端实现排序算法,是很常见的IT知识,题目是对前端行业的调侃。善意的,友好的,符合 新时代思想文化建设的。

本篇文章参考借鉴了以下文章和极客时间数据结构与算法一文,若涉及侵权,请联系我删除。

前言

上一篇文章是关于冒泡排序的学习总结,还未学习的同学可以先看一下以下文章

本节我们一期学习一个新的排序算法,插入排序

带着问题学习

  首先需要思考,一个有序的数组,在往里面插入一个新的数据之后,如何保持数据有序呢?很简单,我们只需要遍历数据,找到数据应该插入的地方,将其插入即可。
在这里插入图片描述
  这是一个动态排序的过程,即动态的往有序集合里添加数据,我们可以通过这种方式,保持集合中的数据一直有序。而对于一组静态数据,我们也可以借鉴上面的插入方法,来进行排序,于是就有了插入排序。

插入排序是如何借助动态思想,实现排序的?

  受限,我们将数组中的数据分为两个区域,已排序区域和未排序区域。初始已排序区间只有一个元素,就是数组的第一个元素。插入算法的核心思想就是 取未排序区间的元素,在已排序区间中找到合适的位置将其插入,并保证已排序区间的数据,一直有序 。重复这个过程,直到未排序区间为空,算法结束。

  如下图所示,要排序的数据是4、5、6、1、3、2,其中左侧为已排序区间,右侧是未排序区间。

在这里插入图片描述
  插叙排序也包含两种操作,一种是 元素的比较 ,一种是 元素的移动 。当我们需要讲一个数据 a 插入到已排序区间时,需要拿 a 与已排序区间的元素依次比较大小,找到合适的插入位置。

  找到插入点后,我们还需要把插入点之后的元素顺序往后移动一位,这样才能腾出位置给 a 元素插入。

  对于不同的查找插入点的方法(从头到尾,从尾到头),元素的比较次数是有区别的,但对于一个给定的初始序列,移动操作次数总是固定的。就等于 逆序度。

  为什么说义移动次数就等于逆序度呢?把上面的例子做一个分析就一目了然了
在这里插入图片描述
  满有序度是 n*(n - 1)/2 = 15,初始序列的有序度是 5 ,所以逆序度是 10 ,插入排序中,数据移动总和等于 10 = 3+3+4。(可以参考上一篇文章 )-- 前端不懂算法(一)–冒泡排序

代码实现

function insertSort(arr) {
    let n =  arr.length;
    if (n<=1) return;
    
    for (let i = 1; i < n; ++i) {

        let value = arr[i];
        let j = i - 1;

        //查找插入位置
        for (; j >= 0; --j) {
            if (arr[j] > value) {
                //数据移动
                arr[j + 1] = arr[j]
            } else {
                break;
            }
        }
        //插入数据
        arr[j+1] = value;
    }
}

Q&A,排序算法的三个问题

第一,插入排序是原地排序算法吗?

  从实现过程可以看出,插入排序算法的运行并不需要额外的存储空间,所以空间复杂度是 O(1),也就是说,这是一个 原地排序算法

第二,插入排序是稳定的排序算法吗?

  在插入排序中,对于值相等的元素,我们可以选择将后面出现的,插入到前面出现元素的后面,这样就可以保证原有的前后顺序不变,所以插入排序是稳定的排序算法。

第三,插入排序的时间复杂度是多少?

  如果要排序的数据已经是有序的,我们并不需要搬移任何数据。如果我们从尾到头在有序数据组里面查找插入位置。每次只需要比较一个数据就能确定插入位置。所以这种情况下,最好的时间复杂度为O(n)。

  如果数组是倒序的,每次插入都相当于在数组的第一个位置插入数据,需要移动大量的数据,所以最坏情况的时间复杂度为O(n2)。

还记得最开始我们讨论的,在数组中插入一个数据的平均时间复杂度是多少吗?没错就是O(n),所以对于插入排序来说,每次插入都相当于在数组中插入一个数据,循环执行 n 次插入操作,所以平均时间复杂度为O(n2)。

选择排序

  选择排序的实现方式有点类似于插入排序,也分已排序区间和未排序区间。但是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。
在这里插入图片描述

选择排序的三个问题

  照例,思考三个问题,前面的解释已经很详细了,这里就直接公布答案。

  选择排序的控件复杂度为O(1),是一种原地排序算法。选择排序的最好情况、最坏情况和平时情况时间复杂度都为O(n2)。有兴趣的同学可以自己分析一下。

  选择排序是稳定的排序算法吗?这个问题需要重点讨论。

  答案是否定的,选择排序是一种不稳定的排序算法。从前面那张图可以看出,选择排序每次都要找出剩余未排序元素中的最小值,然后和前面的元素交换位置,这样就破坏了稳定性。

比如5、8、5、2、9 这组数据,使用选择排序的话,第一次选择 2 ,与第一个 5 交换位置,那么这两个 5 的前后顺序就变了,所以不稳定。正因如此,相对于冒泡排序和插入排序,选择排序就稍稍逊色一些。

排序系列开篇问题

  我在本系列开篇的地方了一个问题,为什么插入排序比冒泡排序更受欢迎?如果不记得的同学可以查看上一篇博文

  我们已经学习了三种排序,其中冒泡排序和插入排序的时间复杂度都是 O(n2)。都是原地排序算法,为什么插入排序比冒泡排序更受欢迎?

  因为之前我们说过,冒泡排序不管怎么优化,其元素交换的次数是一个固定值,是原始数据的逆序度。插入排序也是一样的,不管怎么优化,元素移动的次数也是逆序度。

  但是,从代码实现上来看,冒泡排序的数据交换,要比插入排序的数据移动复杂的多,冒泡需要三个赋值,而插入只需要一个,下面是代码部分:

if (arr[j] > arr[j + 1]) {
  let temp = arr[j];
    arr[j] = arr[j + 1];
    arr[j + 1] = temp;

    //表示有数据交换
    flag = true;
}
if (arr[j] > value) {
	//数据移动
    arr[j + 1] = arr[j]
} else {
    break;
}

  我们把执行一个赋值语句的时间记做单位时间 unit ,然后分别用冒泡排序和插入排序对同一个逆序度是 K 的数组进行排序。用冒泡排序,需要 K 次交换,每次需要三个赋值语句,所以交换操作总耗时就是 3*K unit 而插入排序排序中移动操作只需要 K 个单位时间。

  这个只是理论的推测,大家可以分别用两个方法对一个数组进行排序,计算一下算法执行时间,我以前做过一个随机生成1000个数组,每个数组包含200个数据的实验,冒泡排序大概需要 700ms , 而插入排序只需要 100ms 就能搞定。

当然插入排序也是可以进一步优化的,具体方法可以学习一下 希尔排序

内容小结

  想要分析,评价一个排序算法,需要从执行效率,内存消耗,稳定性三个方面考虑。因此这一节中,我们分析了三种,时间复杂度为 O(n2) 的排序算法
在这里插入图片描述
  上面三种算法中,冒泡排序,选择排序,基本都是停留在理论层面,了解一下,拓展一下思路,实际开发中运用的并不多,但是插入排序还是很有用的,后面讲排序优化的时候,会讲到有些编程语言中的排序函数,就是使用的插入排序算法。

  本文这几个排序算法,代码简单,比较适合小型排序,用起来也很高效。但是在大规模数据排序的时候,时间复杂度还是有点高,需要倾向于下一节的时间复杂度为 O(nlogn)的排序算法。

  文中不足之处,请各位大佬指点。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值