系列文章链接
1.冒泡排序
1.1 原理
冒泡排序是最简单的排序之一,用一句话描述它
在
无序区
通过交换
找出最大的元素,放到有序区
的前面 以无序数组[3,2,4,1]
举例,如果将其中的最大值排到末尾,得到[3,2,1,4]
,[3,2,1]
就是无序区
,[4]
就是有序区
。
这个交换
怎么理解?我们来看一张图
还是以
[3,2,4,1]
这个数组为例,
- 比较
3
和2
,3
较大,交换两者位置,得到[2,3,4,1]
- 继续比较
3
和4
,4
较大,不做处理,得到[2,3,4,1]
- 再比较
4
和1
,4
较大,交换两者位置,得到[2,3,1,4]
。 完成这轮遍历后不难发现,已经将最大值4移动到最右端。
接下来可以对无序区
中的[2,3,1]
重复上面过程,慢慢扩充有序区
,直到无序区
为空。
完成这个过程大约总共需要4
轮遍历,分别是对[2,3,4,1]
、[2,3,1]
、[1,2]
、[1]
的遍历。
1.2 复杂度和稳定性
复杂度可以分为时间复杂度
和额外空间复杂度
, 如果想了解更多可以参考算法的时间与空间复杂度(一看就懂)。
接下来我们推导一下冒泡排序的时间复杂度:
1.2.1 时间复杂度
在刚才的例子,我们在第一轮遍历中,对[2,3,4,1]
进行了3次比较。在其余三轮遍历中,对[2,3,1]
、[1,2]
、[1]
分别进行了2
次、1
次和0
次比较。如果数组的长度为N
,总的执行次数之和为
不难看出是等差数列求和,等差数列的求和公式为
套入公式可得到
因此可以得到时间复杂度为。
1.2.2 额外空间复杂度
冒泡排序所需要的额外空间
不会随着某个变量n的大小而变化,因此它的空间复杂度是O(1)。
1.2.3 稳定性
稳定性指的是在数组排序过程中,值相同的两个元素前后位置有没有发生变化,更多可以参考排序算法的稳定性。
在冒泡排序中过程中,当比较相同的两个值时可以不变更两者位置,所以是稳定
的,可以结合上述例子理解这个结论。
1.3 代码实现
fuction bubleSort(arr) {
var len = arr.length;
for (var i = len - 1; i >= 0; i -= 1) {
for (var j = 0; j < i; j += 1) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1)
}
}
}
return arr;
}
function swap(arr, i, j) {
if (i === j) return;
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
这里交换函数swap
可以看作是一个“彩蛋”,它的作用是:通过异或(^
) 交换数组arr中位置i
和j
的值。对这部分不感兴趣的同学,直接阅读本章小结。
与异或相对的是同或。
因为不常用,因此百分之九十的人都会记不住这两者的区别。笔者提供一个比较容易记忆的口诀,异或是不进位的二进制相加。
异或有以下数学规律:
与
0
异或等于自身: 0 ^ n => n
相同的数异或为0: n ^ n => 0
符合结合律: (a ^ b) ^ c = a ^ (b ^ c) (1)推导
arr[i] = arr[i] ^ arr[j]
arr[j] = arr[i] ^ arr[j]
将第一个式子代入到第二个式子,可得到
arr[j] = (arr[i] ^ arr[j]) ^ arr[j]
= arr[i] ^ (arr[j] ^ arr[j])
= arr[i] ^ 0
= arr[i]
(2)推导
arr[i] = arr[i] ^ arr[j]
arr[j] = arr[i]
arr[i] = arr[i] ^ arr[j]
将前两个式子代入到第三个式子,得到
arr[i] = (arr[i] ^ arr[j]) ^ arr[i]
= (arr[i] ^ arr[i]) ^ arr[j]
= 0 ^ arr[j]
= a[j]
综上可以实现数组中值的交换
注意:
不要对数组中同一个位置的值,执行上面的逻辑。
1.4 本章小结
- 冒泡排序可以理解为,在
无序区
通过交换
找出最大的元素,放到有序区
的前面。 - 冒泡排序的时间复杂度为
- 冒泡排序的额外空间复杂度为
- 冒泡排序不具有稳定性
- 异或可以理解为不进位的二进制相加,可用于实现数组交换等“骚操作”
2.选择排序
2.1 原理
选择排序,是一种简单直观的排序算法,可以理解为
从
无序区
里找一个最小的数放在有序区
的后面 以无序数组[3,2,4,1]
举例,如果将其中的最小值排到前端,得到[1,3,2,4]
,[1]
就是有序区
,[3,2,4]
就是无序区
。假设对
[3,2,4,1]
开始第一轮遍历
- 创建索引
i
指向数组开始的0
位置,用于指向无序区
的第一个数 - 遍历
[3,2,4,1]
,得到最小值为1
,将它和0
位置的数值3
交换 - 此时左边的
有序区
新增数值1
,索引i位置右移一位,无序区
为[3,2,4]
重复上面过程直到无序区
为空。完成这个过程大约总共需要4
轮遍历,分别是对[2,3,4,1]
、[3,2,4]
、[3,4]
、[4]
的遍历。
2.2 复杂度和稳定性
2.2.1 时间复杂度
选择排序的时间复杂度推导方式和冒泡排序相似,在例子中的4轮遍历分别进行了3
、2
、1
和0
次对比。如果数组的长度为N
,时间复杂度为
通过等差数列求和公式
可以得到时间复杂度为
2.2.2 额外空间复杂度
选择排序所需要的额外空间
不会随着某个变量n的大小而变化,因此它的空间复杂度是O(1)。
2.2.3 稳定性
在上面例子中讲到,当在无序区
获得到最小值后,会和无序区
最左边的值交换,这个过程会让原本在后面的值放到前面。
因此,数组中相同的值就有可能交换位置,选择排序
不具有稳定性
2.3 代码实现
function selectSort(arr) {
const len = arr.length;
for (let i = 0; i < len; i++) {
minIndex = i;
for (let j = minIndex; j < len; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
if (minIndex !== i) {
swap(arr, minIndex, i)
}
}
return arr;
}
function swap(arr, i, j) {
if (i === j) return;
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
2.4 本章小结
- 选择排序是从
无序区
里找一个最小的数放在有序区
的后面 - 选择排序的时间复杂度为 O(N ^ 2)
- 选择排序的额外空间复杂度为O(1)
- 选择排序不稳定
3. 插入排序
3.1 原理
有的时候我们容易把插入排序和选择排序记混。插入排序其实和我们玩扑克牌时整理牌的方式一样,笔者建议用这个方法来记忆这个排序算法。用一句话描述就是
把
无序区
的每一个元素,插入到有序区
的合适位置
仍然以无序数组[3,2,4,1]
举例
在一开始[3]
就是有序区,[2,4,1]
是无序区
。
随后无序区
最左边的2
在有序数组中排序,2
与3
比较后,得到有序区为[2,3]
无序区为[4,1]
。
以此类推,直到无序区
为空。完成这个过程大约总共需要3
轮遍历,分别是
- 无序数组
[2,4,1]
中的2
在有序数组[3]
中寻找合适位置,得到有序区
为[2, 3]
- 无序数组
[4,1]
中的4
在有序数组[2,3]
中寻找合适位置,得到有序区
为[2,3,4]
- 无序数组
[1]
中的1在有序数组[2,3,4]
中寻找合适位置,得到有序区
为[1,2,3,4]
3.2 复杂度和稳定性
3.2.1 时间复杂度
如果数组为倒序排列,并且长度为N
,那么无序数组中的每一个数,在有序数组中寻找合适位置的时候,需要进行1
~N-1
次对比。
最大时间复杂度为
通过等差数列求和公式可以得到结果为
3.2.2 额外空间复杂度
插入排序所需要的额外空间
不会随着某个变量n的大小而变化,因此它的空间复杂度是O(1)。
3.2.3 稳定性
在上图可以知道,将无序区的元素插入到有序区时,是从右往左遍历。假如有两个相同的值,先排序的值在有序区中,而后排序的值会排在它的右边,位置没有变化,因此插入排序也是稳定的。
3.3 代码实现
function insertSort(arr) {
for (var i = 0; i < arr.length - 1; i++) {
var minIndex = i;
for (var j = i + 1; j < arr.length; j++) {
if (arr[minIndex] > arr[j]) {
minIndex = j;
}
}
swap(arr, i, minIndex);
}
return arr;
}
function swap(arr, i, j) {
if (i === j) {
return;
}
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
3.4 本章小结
- 插入排序和扑克牌类似,是把
无序区
的每一个元素,插入到有序区
的合适位置 - 插入排序的时间复杂度为
- 插入排序的额外空间复杂度为
- 插入排序具有稳定性