前端不懂算法(一)--冒泡排序

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

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

前言

  在学习编程的时候,大部分人接触的第一个算法应该都是排序。很多编程语言也都提供了排序函数。在平常项目中,我们也经常遇到排序。所以这个《前端不懂算法》系列就以排序开始,一起来学习一下经典排序。
   排序算法种类比较繁杂,每一种排序算法都有特定的使用场景,根据特点,实现原理,时间复杂度,空间复杂度等选择不同的排序方法。
  由于方法众多,有很多没听说过的排序,类似面条排序,猪蹄排序,火锅排序等,本系列我们学习最常用的,冒泡排序,插入排序,选择排序,快速排序,归并排序,桶排序,基数排序。
  本节我们先学习冒泡,插入和选择。
在这里插入图片描述
  带着问题学习,效率最高,先看一个思考题:插入排序和冒泡排序的时间复杂度相同,都是O(n2) 。在实际的软件开发中,为什么更倾向于使用插入排序而不是冒泡排序呢?
  先思考一分钟,然后我们一起学习。

如何分析一个“排序算法”?

  学习一个算法,不光是原理,实现,还得学会如何分析这个算法,对于排序算法,要从以下几个方面入手。

排序算法的执行效率

  排序算法的执行效率分为以下几个方面。

1.最好情况,最坏情况,平均情况时间复杂度

  我们在分析排序算法的时间复杂度的时候,需要分别给出乐观情况,悲观情况,平均情况下的时间复杂度。除此之外还要给出乐观,悲观情况下,原始数据是什么样子的。

为什么要区分三种情况下的时间复杂度呢?

  • 一、有些排序算法会区分,为了好对比,我们都做一下分类讨论。
  • 二、对于排序的顺序,有的接近有序,有的完全无序。有序度不同的数据,对于排序的执行时间肯定有影响。我们需要知道排序算法在不同数据下的表现。
2.时间复杂度的系数,常数,低阶

  我们知道时间复杂度是反映数据规模 n 很大的时候的一个增长趋势,所以它表示的时候会忽略系数,常数,低阶。但是实际的软件开发中,我们排序的可能是10个,100个,1000个这样规模很小的数据,所以,在对同一阶时间复杂度的排序算法性能对比的时候,我们就要把系数,常数,低阶也考虑进来。

3.比较次数和交换(或移动)次数

  冒泡,插入,选择都是基于比较的排序算法执行过程,会涉及两种操作,一种是比较,一种是移动。所以我们在分析排序算法的执行效率的时候,需要把比较次数和交换(或移动)次数也考虑进去。

排序算法的内存消耗

  排序算法的内存消耗可以用空间复杂度来表示,针对排序算法,我们还需要引入一个新的概念 原地排序算法 ,该算法是特指空间复杂度为O(1)的排序算法。本篇所讲的三种排序,都是原地排序算法。

排序算法的稳定性

  仅仅用执行效率和内存消耗来衡量排序算法的好坏是不够的。针对排序算法,还有一个指标, 稳定性 。这个概念的意思是如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。

  举个栗子:有一个数组[1, 3, 7, 3, 5,4],经过排序后会变成[1, 3, 3, 4, 5,7]

  这组数据中有两个3,如果经过排序后,如果这两个3的前后顺序没有改变,那么就称这种排序是 稳定的排序算法 ,如果前后顺序发生改变,那么就是 不稳定的排序算法

  你可能会问,这两个3,谁在前谁在后,有什么问题吗?跟稳定性有什么关系?

  为什么需要考虑排序算法的稳定性,我们举例排序的时候都是拿整数来举例,但是在实际开发中,我们要排序的对象往往不是单纯的整数,而是一组对象,我们需要按照对象的某个key值来排序。

eg:现在需要给电商交易系统中的“订单”排序,该订单有两个属性,一个是下单时间,一个是订单金额,如果订“单数量”有10万条,我们希望按照金额,从小到大排序。对于相同金额的订单,我们希望按照下单时间进行排序,那么对于这样一个需求,应该如何来做?

  最先想到的方法可能是先对 金额 进行排序,然后再分别对于金额相同的数据,进行按 时间 排序,这种思路不难理解,但是执行起来会很复杂。

  如果我们借助稳定排序算法,那么这个问题可以很方便的解决。

思路如下:

  • 先按照下单时间进行排序
  • 然后用稳定算法按照金额再排序一遍

完成,金额相等的订单会按照时间顺序排好,问题来了,为什么?
在这里插入图片描述
  凭什么先排时间,再排金额,就完成了?

  稳定排序可以保证金额相同的两个对象,在排序之后的顺序不变。第一次排序后,所有的订单都按照下单时间排好了,在第二次排序中,我们用的是稳定排序,相同金额的两个对象,不会改变顺序,所以之前按时间排好的顺序,不会被打乱。
在这里插入图片描述

冒泡排序

  冒泡排序只会操作临近的两个数据,每次冒泡操作都会对相邻的两个数据进行比较,看是否满足大小关系要求,如果不满足,就让他俩互换。一次冒泡至少会让一个元素移动到它应该在的位置,重复n次,就完成了n个数据的排序工作。

  举个例子现在要对一组数据进行排序,4、5、6、3、2、1,从小到大进行排序,第一次的冒泡过程是这样的:

在这里插入图片描述
  从上图可以看出,经过一次冒泡操作后,6这个元素已经存储在正确的位置上了。想要完成所有数据的排序,只要经过6次这样的冒泡就可以了。
在这里插入图片描述

  实际上冒泡排序还可以优化,当某次冒泡操作没有数据交换时,说明已经达到完全有序,不用再继续执行后续的冒泡操作,下面的例子只需要4次冒泡就可以完成。
在这里插入图片描述
  冒泡排序算法原理还是比较清晰易懂的,以下是代码部分,可结合原理图查看

function bubbleSorting(arr) {
	let n =  arr.length;
	
	if (n<=1) return;
	
	for (let i = 0; i < n; ++i) {
	    //设立提前退出标志位
	    let flag = false;
	    for (let j = 0; j < n - i - 1; ++j) {
	        //交换
	        if (arr[j] > arr[j + 1]) {
	            let temp = arr[j];
	            arr[j] = arr[j + 1];
	            arr[j + 1] = temp;
	
	            //表示有数据交换
	            flag = true;
	        }
	    }
	    //没有数据交换,提前退出
	    if (!flag) break;
	}
}

  结合上文中分析排序算法的三个方面,现在有三个问题

Q1,冒泡排序是原地排序吗?

  • 冒泡排序只涉及相邻的数据交换,只需要常量级的临时空间,所以空间复杂度为O(1)。

Q2,冒泡排序是稳定的排序算法吗?

  • 在冒泡排序中只有交换才能改变两个元素的前后顺序,为了保证冒泡排序的稳定性,当两个元素大小相等的时候,我们不做交换处理,相同大小的数据在排序前后顺序不会改变,所以是稳定排序。

Q3,冒泡排序的时间复杂度是多少?

  • 最好情况下,要排序的数据已经是有序的了,我们只要一次冒泡就可以结束,所以最好情况时间复杂度是O(n),最坏的情况,要排序的数列完全是倒序的,我们需要进行n次冒泡,所以最坏情况时间复杂度O(n2)。
    在这里插入图片描述

  最好最坏情况下,时间复杂度都比较容易区分,那平均情况下时间复杂度是多少呢?

  这个时候需要结合概率论的知识点,平均时间复杂度就是加权平均期望时间复杂度

  对于包含 n 个数据的数组,这个数据就有 n! 种排列方式。不同的排列方式,冒泡执行的时间肯定是不同的。如果用概率论的定量分析平均时间复杂度,推导过程比较复杂(好吧是我不会,等我研究明白了帮你们推一遍o(╥﹏╥)o)。

  这里我们换一种思路,通过“有序度”, “逆序度”的概念来分析。

  有序度是数组中具有有序关系的元素对的个数,有序元素的数学表达式如下:

  有序元素对:a[i] <= a [j] ,如果 i < j
在这里插入图片描述
  同理,对于一个倒序排序的数组,比如6、5、4、3、2、1,有序度就是 0 ,对于一个完全有序的数组,比如1、2、3、4、5、6,有序度就是 n*(n-1)/2 ,也就是 15 ,这种完全有序的数组的有序度,称为 满有序度

  逆序度的原理正好相反,逆序元素对:a[i] > a [j] ,如果 i < j

  关于上面的概念,还有一个公式:逆序度 = 满有序度 - 有序度。排序的过程就是增加有序度,减少逆序度的过程,达到满有序度后,排序就完成了。

  前面的例子,4、5、6、3、2、1,其中有序元素对为 (4,5),(4,6),(5,6)所以有序度是 3, n = 6 ,所以排序完成之后,最终的满有序度为 n*(n-1)/2 = 15

在这里插入图片描述
  冒泡排序包含两个操作原子, 比较交换。每交换一次,有序度就 +1 。不管算法怎么改进,交换次数是确定的,即为 逆序度 也就是n*(n-1)/2 —初始有序度,此例要经过12次交换。

  对于包含 n 个数组的数组进行冒泡排序,平均交换次数是多少呢?最坏状态下,初始状态有序度是 0,所以要进行 n*(n-1)/2 次交换,最好情况下,不需要进行交换,我们可以去一个中间值 n*(n-1)/4 来表示有序度既不高也不低的情况。

  换句话说,平均情况下,需要 n*(n-1)/4 交换 操作, 比较 操作肯定要比 交换 操作要多,而且复杂度的上限是O(n2),所以平均情况下时间复杂度就是O(n2)。

  这种平均复时间复杂度的推导过程并不严谨,但是很实用。概率论定量分析太复杂,不太好用。后续博文会有快排的学习,到时候我们还会用这种“不严格”的方式来分析平均时间复杂度。

以上是我准备的系列文章《前端不懂算法》系列的第一篇,后续还会有不同算法的学习总结,欢迎各位大佬指点。

  文章跳转

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值