冒泡排序——像泡泡一样浮上来
中等8语言版本:C++
概述:通过比较相邻元素并交换位置,让最大的数像水中的气泡一样慢慢“浮”到数组末尾。
冒泡排序:像气泡一样浮上来
冒泡排序是一种非常直观的排序方法,特别适合初学编程的同学理解“排序”这个概念。它的思路就像倒一杯汽水——气泡会从杯底慢慢往上浮,越大的气泡浮得越高。在冒泡排序里,我们通过不断比较相邻的两个数,把较大的数像气泡一样“顶”到数组的末尾。
1. 基本思想:两两比较,大的后移
想象一下体育课排队,老师让你们按身高从矮到高站好。现在队伍是乱序的:你前面的人比你高,你就和他交换位置,让他往后站;你再和下一个比较……一轮下来,最高的人就会站到最后。接着,忽略最后那个最高的人,对剩下的人重复同样的操作,直到整个队伍排好。
用计算机的话说,冒泡排序的做法是:
- 从头到尾,依次比较相邻的两个元素,如果前一个比后一个大,就交换它们。
- 经过一轮(一次完整的从头到尾的比较),最大的数就会被移到数组的最后。
- 下一轮时,最后那个数已经就位,不用再管它,所以比较范围减少一个。
- 重复这个过程,直到所有数都排好。
2. 动手演练:对 [5, 3, 8, 1] 排序
我们拿数字来走一遍流程:
原始数组:[5, 3, 8, 1]
第一轮(把最大的 8 浮到最后):
- 比较
5和3:5 > 3,交换 →[3, 5, 8, 1] - 比较
5和8:5 < 8,不交换 →[3, 5, 8, 1] - 比较
8和1:8 > 1,交换 →[3, 5, 1, 8]
✅ 这一轮结束,8已经放到了最后的位置。
第二轮(忽略最后的 8,在 [3, 5, 1] 中把最大的 5 浮到正确位置):
- 比较
3和5:3 < 5,不交换 →[3, 5, 1, 8] - 比较
5和1:5 > 1,交换 →[3, 1, 5, 8]
✅ 这一轮结束,5已经放到了倒数第二的位置。
第三轮(忽略最后的 5 和 8,在 [3, 1] 中把最大的 3 浮到正确位置):
- 比较
3和1:3 > 1,交换 →[1, 3, 5, 8]
✅ 所有数字排好,排序完成!
可以看到,一共需要 n-1 轮(这里 n=4,所以 3 轮),每一轮比较的次数逐渐减少。
3. 用 C++ 代码实现
下面是最标准的冒泡排序代码,每一行都有中文注释,方便你理解。
#include <iostream>
using namespace std;
// 冒泡排序函数,参数:数组 arr,数组元素个数 n
void bubbleSort(int arr[], int n) {
// 外层循环:需要 n-1 轮(最后一个数自动就位)
for (int i = 0; i < n - 1; i++) {
// 内层循环:每轮比较范围缩小,因为最后 i 个数已经排好
for (int j = 0; j < n - 1 - i; j++) {
// 如果前一个数比后一个数大,交换它们
if (arr[j] > arr[j + 1]) {
// 交换两个相邻的数,用临时变量暂存
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main() {
// 定义一个整型数组并初始化,存储要排序的数字
int arr[] = {5, 3, 8, 1};
// 计算数组元素个数
int n = 4;
// 调用冒泡排序函数
bubbleSort(arr, n);
// 输出排序后的数组
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
return 0;
}
运行这段代码,屏幕上会输出:
1 3 5 8
4. 新手最容易犯的错误
写冒泡排序时,有几个地方特别容易出错:
- 内层循环的边界写错:
j < n - 1 - i是最正确的写法。如果写成j < n - i,当 j 等于 n-1-i 时,arr[j+1]就会访问到数组外面(越界),程序可能崩溃。记住:最后一轮比较时,j 最大只能到 n-2-i。 - 交换代码漏写:有些人只写比较条件,却忘了写交换的三行语句,结果数组根本没变。
- 忘记外层循环:只写一次内层循环,最多只能把最大的数浮到最后,后面的数没有机会排序。一定要用外层循环控制轮数。
- 数组越界:在比较时一定要保证
j+1是有效索引,所以内层循环条件必须用< n - 1 - i(最安全)或等价的<= n - 2 - i。
5. 完整可运行示例(加了一点交互)
如果你想自己试试不同数字,可以把 main 函数稍微改一改,变成手动输入:
#include <iostream>
using namespace std;
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main() {
// 先让用户告诉我们要排几个数
int n;
cout << "请输入要排序的数字个数:";
cin >> n;
// 定义数组,大小由用户输入决定(注意:标准C++中数组大小必须是常量,这里用动态分配更好,但为简单起见用较大的静态数组)
int arr[100]; // 假设最多100个数
cout << "请输入 " << n << " 个整数,用空格隔开:";
for (int i = 0; i < n; i++) {
cin >> arr[i];
}
bubbleSort(arr, n);
cout << "排序后的结果:";
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
6. 冒泡排序的特点:简单但慢
- 优点:代码简单,容易理解和记忆,适合刚学排序的同学。
- 缺点:效率低,当数据量很大时(比如成千上万个数字),它会做很多不必要的比较和交换。在最坏情况下(数组完全反序),比较次数达到 n(n-1)/2,时间复杂度为 O(n²)。
- 改进小技巧:如果在某一轮内层循环中没有发生任何交换,说明数组已经有序,可以提前退出。但这仍然不会改变最坏情况的时间复杂度。
7. 学完冒泡排序,下一步可以学什么?
- 选择排序:另一种简单排序,每次找到最小值放到前面,代码也很直观。
- 插入排序:像打扑克牌一样,把新拿到的牌插入到已排好的序列中。
- 快速排序:实际应用中最常用的排序,速度比冒泡快得多,但理解起来稍难一点。
- 时间复杂度:了解大O表示法,知道为什么冒泡排序是 O(n²),而快速排序是 O(n log n)。
如果你已经理解了冒泡排序,可以试着亲手写一遍,再用不同的数字测试,看看输出对不对。编程就像搭积木,从简单的冒泡开始,慢慢就能搭建更复杂的算法大厦啦!
例题精讲
1单选题
下列关于冒泡排序的说法中,正确的是?
A每趟排序都能确定一个最小元素的位置
B每趟排序都能确定一个最大元素的位置
C对n个元素排序,总共需要n趟
D排序过程中只允许交换相邻元素
2判断题
冒泡排序是一种稳定的排序算法。
3填空题
以下函数实现冒泡排序,请填写外层循环条件。
void bubbleSort(int arr[], int n) {
for (int i = 0; i < ___; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j+1]) {
swap(arr[j], arr[j+1]);
}
}
}
}4单选题
对冒泡排序进行优化,如果在某一趟排序中没有发生任何交换,则说明?
A数组已经有序,可以提前结束排序
B数组处于逆序状态
C还需要继续排序
D该趟排序后只能确定一个元素的位置
5判断题
冒泡排序的平均时间复杂度是O(n²)。