CC++ & Algorithm

冒泡排序——像泡泡一样浮上来

中等8
语言版本:C++
概述:通过比较相邻元素并交换位置,让最大的数像水中的气泡一样慢慢“浮”到数组末尾。

冒泡排序:像气泡一样浮上来

冒泡排序是一种非常直观的排序方法,特别适合初学编程的同学理解“排序”这个概念。它的思路就像倒一杯汽水——气泡会从杯底慢慢往上浮,越大的气泡浮得越高。在冒泡排序里,我们通过不断比较相邻的两个数,把较大的数像气泡一样“顶”到数组的末尾。


1. 基本思想:两两比较,大的后移

想象一下体育课排队,老师让你们按身高从矮到高站好。现在队伍是乱序的:你前面的人比你高,你就和他交换位置,让他往后站;你再和下一个比较……一轮下来,最高的人就会站到最后。接着,忽略最后那个最高的人,对剩下的人重复同样的操作,直到整个队伍排好。

用计算机的话说,冒泡排序的做法是:

  • 从头到尾,依次比较相邻的两个元素,如果前一个比后一个大,就交换它们。
  • 经过一轮(一次完整的从头到尾的比较),最大的数就会被移到数组的最后。
  • 下一轮时,最后那个数已经就位,不用再管它,所以比较范围减少一个。
  • 重复这个过程,直到所有数都排好。

2. 动手演练:对 [5, 3, 8, 1] 排序

我们拿数字来走一遍流程:

原始数组[5, 3, 8, 1]

第一轮(把最大的 8 浮到最后):

  • 比较 53:5 > 3,交换 → [3, 5, 8, 1]
  • 比较 58:5 < 8,不交换 → [3, 5, 8, 1]
  • 比较 81:8 > 1,交换 → [3, 5, 1, 8]
    ✅ 这一轮结束,8 已经放到了最后的位置。

第二轮(忽略最后的 8,在 [3, 5, 1] 中把最大的 5 浮到正确位置):

  • 比较 35:3 < 5,不交换 → [3, 5, 1, 8]
  • 比较 51:5 > 1,交换 → [3, 1, 5, 8]
    ✅ 这一轮结束,5 已经放到了倒数第二的位置。

第三轮(忽略最后的 5 和 8,在 [3, 1] 中把最大的 3 浮到正确位置):

  • 比较 31: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²)。