对于排序算法的初学者来说,冒泡排序因其算法思想非常直观,被认为是八大排序算法中最简单、最易于理解的一个。
🔍 算法思想与步骤
冒泡排序的核心思想是重复地遍历待排序的列表,一次比较两个相邻的元素,如果它们的顺序错误(例如,在升序排序中前一个大于后一个),就将它们交换过来。通过每一轮的遍历,最大(或最小)的元素会像气泡一样逐渐“浮”到数列的顶端,因此得名“冒泡排序”。
其具体步骤如下:
- 比较相邻元素:从列表的第一个元素开始,比较相邻的两个元素。
- 交换错误顺序:如果第一个元素比第二个元素大(对于升序排序),就交换它们的位置。
- 移动至下一对:接着比较下一对相邻元素,重复步骤2,一直进行到列表的末尾。经过这一轮,最大的元素就已经被“冒泡”到了列表的最后一位。
- 重复过程,缩小范围:针对除最后一个(已排好的)元素之外的所有元素,重复上述步骤。每完成一轮,需要比较的元素就减少一个。
- 排序完成:持续进行上述过程,直到没有任何一对数字需要比较,此时列表排序完成。
💻 算法实现
下面是冒泡排序的一个基础实现示例(使用类似C语言的语法),它清晰地展示了算法的双循环结构:
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n-1; i++) { // 外层循环控制排序的轮数
for (int j = 0; j < n-i-1; j++) { // 内层循环进行相邻元素的比较和交换
if (arr[j] > arr[j+1]) { // 如果顺序错误,则交换
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
- 外层循环 (i):负责控制排序总共需要进行多少轮。对于一个有
n个元素的列表,最多需要n-1轮。 - 内层循环 (j):负责在每一轮中进行实际的“比较和交换”工作。注意
j < n-i-1,这是因为每经过一轮,列表末尾的i个元素已经是排好序的,无需再比较。
⏱️ 算法特性
- 时间复杂度:冒泡排序的平均时间复杂度和最坏时间复杂度都是 O(n²)。这意味着当数据量增大时,排序所需时间会成平方级增加,因此它只适用于小规模数据的排序。
- 空间复杂度:为 O(1)。因为它只需要一个临时变量用于交换,属于原地排序,不占用额外的内存空间。
- 稳定性:冒泡排序是一种稳定的排序算法。也就是说,如果存在两个相等的元素,排序后它们的相对顺序不会改变。这是因为只有在相邻元素顺序错误时才交换,相等的元素不会相互交换。
🧩 示例演示
以数组 [5, 3, 8, 6, 2]的升序排序为例,过程如下:
-
第一轮:
- 比较 5和3 → 交换 → [3, 5, 8, 6, 2]
- 比较 5和8 → 不交换 → [3, 5, 8, 6, 2]
- 比较 8和6 → 交换 → [3, 5, 6, 8, 2]
- 比较 8和2 → 交换 → [3, 5, 6, 2, 8] (最大数8就位)
-
第二轮:在未排序部分
[3, 5, 6, 2]中重复过程,将 6 交换到正确位置 → [3, 5, 2, 6, 8] -
第三轮:在
[3, 5, 2]中,将 5 交换到正确位置 → [3, 2, 5, 6, 8] -
第四轮:在
[3, 2]中,交换 → [2, 3, 5, 6, 8] -
排序完成:
[2, 3, 5, 6, 8]
💎 简单对比与其他简单排序
除了冒泡排序,选择排序和插入排序也常被归为简单排序算法,时间复杂度也都是O(n²)。它们的主要区别在于:
- 选择排序:思路是每次从未排序部分中找到最小(或最大)元素,放到已排序序列的末尾。它的交换次数比冒泡排序少。
- 插入排序:思路类似于整理扑克牌,将未排序的元素逐个插入到已排序序列的合适位置。对于部分有序的列表,插入排序效率较高。
在这三者中,冒泡排序的算法逻辑通常被认为是最直接、最容易理解和实现的,这也是它成为经典入门算法的原因。