冒泡排序算法

对数组进行 冒泡排序 ,是算法当中比较简单易理解的。

冒泡

原理

数组中有 n 个数,比较每相邻两个数,如果前者大于后者,就把两个数交换位置;这样一来,第一轮就可以选出一个最大的数放在最后面;那么经过 n-1(数组的 length - 1) 轮,就完成了所有数的排序。

首先,通过一次循环,从而找到最大数放于数组队尾

1
2
3
4
5
6
7
8
9
let arr = [3,4,1,2];
// 遍历数组,次数就是arr.length - 1
for (let i = 0; i < arr.length - 1; i++) {
// 如果前一个数 大于 后一个数 就交换两数位置
if (arr[i] > arr[i + 1]) {
[arr[i],arr[i + 1]] = [arr[i + 1], arr[i]];
}
}
console.log(arr) // [3, 1, 2, 4]

总计,重复 arr.length - 1 次,便可以实现数组按从小到大的顺序

1
2
3
4
5
6
7
8
9
10
11
let arr = [3,4,1,2];
// 遍历数组,次数就是arr.length - 1
for (let j = 0; j < arr.length - 1; j++) {
// 这里 i < arr.length - 1 ,要思考思考合适吗?我们下面继续说
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] > arr[i + 1]) {
[arr[i],arr[i + 1]] = [arr[i + 1], arr[i]];
}
}
}
console.log(arr) // [1,2,3,4]

虽然上面的代码已经实现冒泡排序了,但就像注释中提到的,内层 for 循环的次数写成,i < arr.length - 1 ,是不是合适呢?
我们想一下,当第一次,找到最大数,放到最后,那么下一次,遍历的时候,是不是就不能把最后一个数算上了呢?因为他就是最大的了,不会出现,前一个数比后一个数大,要交换位置的情况,所以内层 for 循环的次数,改成 i < arr.length - 1 -j ,才合适,看下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
let arr = [3, 4, 1, 2];
function bubbleSort (arr) {
for (let j = 0; j < arr.length - 1; j++) {
// 这里要根据外层for循环的 j,逐渐减少内层 for循环的次数
for (let i = 0; i < arr.length - 1 - j; i++) {
if (arr[i] > arr[i + 1]) {
[arr[i],arr[i + 1]] = [arr[i + 1], arr[i]];
}
}
}
return arr;
}
bubbleSort(arr);

此外,假如提前排序完成,但还会继续执行排序,因此需要一个标志位来判断是否提前结束排序,从而优化算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let arr = [3, 4, 1, 2];
function bubbleSort (arr) {
let max = arr.length - 1;
for (let j = 0; j < max; j++) {
// 声明一个变量,作为标志位
let done = true;
for (let i = 0; i < max - j; i++) {
if (arr[i] > arr[i + 1]) {
[arr[i],arr[i + 1]] = [arr[i + 1], arr[i]];
done = false;
}
}
if (done) {
break;
}
}
return arr;
}
bubbleSort(arr);

性能

时间复杂度: 平均时间复杂度O(n*n)、最好情况O(n)、最差情况O(n*n)
空间复杂度: O(1)
稳定性: 稳定

时间复杂度指的是一个算法执行所耗费的时间
空间复杂度指运行完一个程序所需内存的大小
稳定指,如果a=b,a在b的前面,排序后a仍然在b的前面
不稳定指,如果a=b,a在b的前面,排序后可能会交换位置

总结

  1. 外层 for 循环控制循环次数
  2. 内层 for 循环进行两数交换,找每次的最大数,排到最后
  3. 设置一个标志位,减少不必要的循环