写在前面的话
首先声明强调所有视频均来自于网站:https://visualgo.net/zh ,这是一个通过动画可视化数据结构和算法的网站,非常有助于大家理解和学习数据结构和算法
冒泡排序是一种简单的排序算法,采用重复遍历待排序的数列,比较相邻元素,并将其顺序错乱的元素交换过来,直到没有需要交换的元素为止。因其在每一轮中都将当前未排序部分中的最大(或最小)的元素"冒泡"到数列的一端而得名。
冒泡排序的基本过程
- 比较相邻元素:从数组的第一个元素开始,比较当前元素与下一个元素。如果它们的顺序错误(例如,在升序排序中,当前元素大于下一个元素),则交换它们。
- 重复遍历:对每一对相邻元素执行上述操作,直到整个数组没有需要交换的元素为止。遍历的次数通常是数组的长度减一。
- 优化:如果在某一轮遍历中没有进行任何交换操作,则说明数组已经排序完成,可以提前终止算法。
伪代码
以下是冒泡排序的伪代码示例:
function bubbleSort(arr)
n = length(arr) // 获取数组长度
for i from 0 to n-1 do // 外循环,控制轮数
swapped = false // 初始化交换标志为 false
for j from 0 to n-i-2 do // 内循环,比较相邻元素
if arr[j] > arr[j+1] then
swap(arr[j], arr[j+1]) // 交换元素
swapped = true // 发生交换,标记为 true
end if
end for
if not swapped then // 如果没有发生交换
break // 提前结束排序
end if
end for
end function
冒泡排序是一种基础的排序算法,其主要思想是通过相邻元素的比较和交换,将未排序部分的最大(或最小)元素逐步移动到已排序部分的边界。该算法的名字来源于其工作方式,就像气泡一样,较大的元素逐渐"冒泡"到数组的顶部。
外层循环的范围 0 to n-1
- 目的:外层循环的目的是控制排序的轮数。由于每完成一轮,最大的元素就会被"冒泡"到数组的末尾,因此在每一轮中,我们可以减少比较的次数。
- 具体解释 :外层循环从
0
到n-1
,实际上只需要执行n-1
次,因为在第n-1
次遍历时,所有元素都已经有序,不再需要进行比较。
内循环的范围 0 to n-i-2
- 原因 :内循环的范围是
0
到n-i-2
,这是因为:- 每完成一轮外循环,最大的元素会被放到数组的最后位置。因此,下一轮排序时,最后一个元素不需要再参与比较。
n-i-1
是最后一个元素的索引,而n-i-2
是倒数第二个元素的索引。因此,内循环只需比较到n-i-2
以确保不会越界。- (网络上也常常有关于
0
ton-1-j
的范围叙述)
表达式的详细解释
n-i-2
- 解释 :
- 在数组中,外循环的变量
i
表示当前轮数。当i=0
时,我们比较整个数组,直到n-1
。 - 当
i=1
时,我们只需要比较到n-2
,因为n-1
位置上的元素在上一次比较中已确定是当前最大元素(已经排序)。 - 因此,
n-i-2
是,需要比较的最后一个元素的索引(不包括n-1
位置,即最大的已被排好序的元素)。
- 在数组中,外循环的变量
n-1-j
- 解释 :
- 这个表达式表示的是当前内循环正在比较的元素的索引的反向表示。
- 当
j
从0
开始增加时,n-1-j
则从n-1
降到n-2
,表示未排序部分最后一个元素的位置。 - 当
j
达到n-i-2
时,n-1-j
就是数组的最后一个索引。
1. 范围的动态变化
n-i-2
是用来确定内循环的上限,随着i
的增加,比较的次数减少,反映已经确定的位置。n-1-j
则是记录了内循环当前所比较的最后元素,从大到小推进。
2. 等价性
-
当
j
增加并且达到其最大值n-i-2
,此时n-1-j
会反映出数组底部已确立的位置,简化了不再需要比较的过程。 -
视角不同 :
n-i-2
是从未排序部分考虑的上限,而n-1-j
则是从已排序部分和遍历过程中考虑的当前元素位置。 -
有效性:这两个表达式共同决定着当前需要比较的元素,维护算法逻辑上的有效性,同时保证只有必要的元素被比较,避免不必要的重复。
swapped
变量的使用
- 作用 :
swapped
变量用于记录在当前轮次内是否发生了元素的交换。如果在某一轮中没有任何交换,说明数组已经是有序的,可以提前结束排序,从而提高算法效率。 - 初始化 :在每一轮开始时,将
swapped
初始化为false
。如果在内循环中发生了交换,则将其设为true
。 - 提前结束 :在内循环结束后,检查
swapped
的值。如果仍为false
,则说明没有发生任何交换,数组已经排序完成,直接跳出外循环。
冒泡排序
实例解析
假设我们有一个数组 [5, 3, 8, 4, 2]
,我们使用冒泡排序对其进行升序排序。
第一步:初始数组
[5, 3, 8, 4, 2]
第一轮排序
- 比较 5 和 3,5 > 3,交换:
[3, 5, 8, 4, 2]
- 比较 5 和 8,5 < 8,不交换。
- 比较 8 和 4,8 > 4,交换:
[3, 5, 4, 8, 2]
- 比较 8 和 2,8 > 2,交换:
[3, 5, 4, 2, 8]
当前第一轮结束,最大元素 8 已"冒泡"到数组的最终位置。
第二轮排序
[3, 5, 4, 2, 8]
- 比较 3 和 5,3 < 5,不交换。
- 比较 5 和 4,5 > 4,交换:
[3, 4, 5, 2, 8]
- 比较 5 和 2,5 > 2,交换:
[3, 4, 2, 5, 8]
第二轮结束,5 已在正确位置。
第三轮排序
[3, 4, 2, 5, 8]
- 比较 3 和 4,3 < 4,不交换。
- 比较 4 和 2,4 > 2,交换:
[3, 2, 4, 5, 8]
第三轮结束。
第四轮排序
[3, 2, 4, 5, 8]
- 比较 3 和 2,3 > 2,交换:
[2, 3, 4, 5, 8]
到此为止,数组已经完全有序,排序结束。
时间复杂度
冒泡排序的时间复杂度为:
- 最坏情况:O(n^2)(当数组完全逆序时)
- 平均情况:O(n^2)
- 最好情况:O(n)(当数组已经有序时,优化后可提前结束)
虽然冒泡排序实现简单,且对小规模数据表现良好,但由于其较慢的性能,常用于教学和演示排序算法的基础。