冒泡排序(Bubble Sort):是一种简单的排序算法,也是一种稳定的排序算法。其实现原理是:依次比较两个相邻的元素,当该对元素顺序不正确时就进行交换,从左到右一轮遍历得到一个最值;重复此步骤,直到没有任何两个相邻的元素可以交换,就表明完成了排序。
简单来说,就是两两比较,不断交换,逐个推进,一轮遍历得到一个最值的方式来排序的。
冒泡排序的示例:
假设待排序序列为 5、1、4、2、8,如果采用冒泡排序对其进行升序排序,则整个排序过程如下所示:
第一轮排序能找到最大值且通过交换将其放置到最后一位,第二轮排序能找到第二大值且通过交换将其放置到倒数第二一位,第三轮排序能找到第三大值且通过交换将其放置到倒数第三一位。依次类推。
-
第一轮遍历:此时整个序列中的元素都位于待排序序列,依次比较每对相邻的元素,并对顺序不正确的元素进行位置交换。
比较 4 次,从待排序序列中找到此次的最值 8,并将其放到了待排序序列的尾部,并入已排序序列中。
-
第二轮遍历:此时待排序序列只包含前 4 个元素,依次比较每对相邻元素,并对顺序不正确的元素进行位置交换。
比较 3 次,从待排序序列中找到此次的最值 5,并将其放到了待排序序列的尾部,并入已排序序列中。
-
第三轮排序:此时待排序序列包含前 3 个元素,依次比较每对相邻元素,并对顺序不正确的元素进行位置交换。
比较 2 次,从待排序序列中找到此次的最值 4,并将其放到了待排序序列的尾部,并入已排序序列中。
-
第四轮遍历:此时待排序序列包含前 2 个元素,对其进行比较,并当顺序不正确时进行交换。
比较 1 次,从待排序序列中找到此次的最值,并将其放到了待排序序列的尾部,并入已排序序列中。
-
当进行第五轮遍历时,由于待排序序列中仅剩 1 个元素,无法再进行相邻元素的比较,因此直接将其并入已排序序列中,此时的序列就为已排序好的序列。
冒泡排序的效率:
冒泡排序运行效率较低。
比较次数:
上面的例子一共有 5 个数字,第一轮遍历进行了 4 次比较,第二轮遍历进行了 3 次比较,第三轮遍历进行了 2 次比较,第四轮遍历进行了 1 次比较,比较次数为:4 + 3 + 2 + 1
。
假设有 n 个数据项,推导公式为:(n - 1) + (n - 2) + (n - 3)+ ... +1 = n * (n - 1) / 2
,因此冒泡排序真实的比较次数为 n * (n - 1) / 2
。
n * (n - 1) / 2 = n² / 2 - n / 2
,根据推导大 O 表示法的规则 2 只保留最高阶项,变为 n² / 2
,根据规则 3 去除最高阶项的常量,变为 n²
,因此冒泡排序的比较次数的大 O 表示法为 O(n²)
。
交换次数:
因为不可能每次比较都交换一次,因此假设有两次比较才交换一次,因此冒泡排序真实的交换次数为 n * (n - 1) / 4
。
由于常量不算在大 O 表示法中,因此可以认为冒泡排序的交换次数的大 O 表示法也为 O(n²)
。
冒泡排序的代码实现:
需要两层循环来实现:
- 外层循环是有 n 个元素就进行 n -1 次内层循环,寻找 n - 1 遍最值。
- 内层循环是两两比较相邻的元素,寻找到每次的最值并通过两两交换将其排到后面。
下一次进行内层循环的时候就不需要比较已被找到的最值了,因此内层循环遍历的数据量是依次递减的。
// 冒泡排序:以升序为例
ArrayList.prototype.bubbleSort = function () {
// 1. 获取列表长度
var length = this.array.length
// 2. 外层循环:第一次循环 i = length - 1,第一次循环 i = length - 2,直到 i = 0,每次内层循环都能找到一个最值并将其放置到末端,因此外层循环从大到小遍历,每次减少末端的已被放置的一个最值
for (var i = length - 1; i >= 0 ; i--) {
// 3. 内层循环,两两比较元素
for (var j = 0; j < i; j++) {
// 3.1. 比较两个相邻的元素,如果前一个元素大于后一个元素,那么交换两个元素的位置
if (this.array[j] > this.array[j+1]) {
this.swap(j, j+1)
}
}
}
}
// 测试冒泡排序:
list.bubbleSort()
console.log(list.toString()) // 1 2 4 5 8