JavaScript 数据结构与算法之美 - 冒泡排序、插入排序、选择排序

JavaScript 数据结构与算法之美

全栈修炼

1. 前言

算法为王。

想学好前端,先练好内功,只有内功深厚者,前端之路才会走得更远

笔者写的 JavaScript 数据结构算法之美 系列用的语言是 JavaScript ,旨在入门数据结构与算法和方便以后复习。

之所以把冒泡排序、选择排序插入排序放在一起比较,是因为它们的平均时间复杂度都为 O(n2)。

请大家带着问题:为什么插入排序比冒泡排序更受欢迎 ?来阅读下文。

2. 如何分析一个排序算法

复杂度分析是整个算法学习的精髓。

时间复杂度: 一个算法执行所耗费的时间。

空间复杂度: 运行完一个程序所需内存的大小。

时间和空间复杂度的详解,请看 JavaScript 数据结构与算法之美 - 时间和空间复杂度。

学习排序算法,我们除了学习它的算法原理、代码实现之外,更重要的是要学会如何评价、分析一个排序算法。

分析一个排序算法,要从 执行效率、内存消耗、稳定性 三方面入手。

2.1 执行效率

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

我们在分析排序算法的时间复杂度时,要分别给出最好情况、最坏情况、平均情况下的时间复杂度。
除此之外,你还要说出最好、最坏时间复杂度对应的要排序的原始数据是什么样的。

2. 时间复杂度的系数、常数 、低阶

我们知道,时间复杂度反应的是数据规模 n 很大的时候的一个增长趋势,所以它表示的时候会忽略系数、常数、低阶。

但是实际的软件开发中,我们排序的可能是 10 个、100 个、1000 个这样规模很小的数据,所以,在对同一阶时间复杂度的排序算法性能对比的时候,我们就要把系数、常数、低阶也考虑进来。

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

这一节和下一节讲的都是基于比较的排序算法。基于比较的排序算法的执行过程,会涉及两种操作,一种是元素比较大小,另一种是元素交换或移动。

所以,如果我们在分析排序算法的执行效率的时候,应该把比较次数和交换(或移动)次数也考虑进去。

2.2 内存消耗

也就是看空间复杂度。

还需要知道如下术语:

内排序:所有排序操作都在内存中完成;

外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;

原地排序:原地排序算法,就是特指空间复杂度是 O(1) 的排序算法。
其中,冒泡排序就是原地排序算法。

2.3 稳定性

稳定:如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。
比如: a 原本在 b 前面,而 a = b,排序之后,a 仍然在 b 的前面;

不稳定:如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序改变。
比如:a 原本在 b 的前面,而 a = b,排序之后, a 在 b 的后面;

3. 冒泡排序

冒泡

思想

冒泡排序只会操作相邻的两个数据。

每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。

一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。

特点

优点:排序算法的基础,简单实用易于理解。

缺点:比较次数多,效率较低。

实现

// 冒泡排序(未优化) const bubbleSort = arr => { console.time('改进前冒泡排序耗时'); const length = arr.length; if (length <= 1) return; // i < length - 1 是因为外层只需要 length-1 次就排好了,第 length 次比较是多余的。 for (let i = 0; i < length - 1; i++) { // j < length - i - 1 是因为内层的 length-i-1 到 length-1 的位置已经排好了,不需要再比较一次。 for (let j = 0; j < length - i - 1; j++) { if (arr[j] > arr[j + 1]) { const temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } console.log('改进前 arr :', arr); console.timeEnd('改进前冒泡排序耗时'); };

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zywdwd.html