首页/数据结构/07-sorting/冒泡排序 🔗 在 Obsidian 中打开
数据结构 · 07-sorting

冒泡排序

难度 ★★重要度 ★★ 考查频率 低题型 算法 / 综合应用 数据结构/排序交换排序408考研
速查
相邻元素两两比较,逆序则交换,每趟把最值"浮"到一端。稳定、原地($O(1)$)、可作用于链表。最好 $O(n)$(已序),平均 / 最坏 $O(n^2)$

速查

项目
主题冒泡排序
核心概念相邻两个元素两两比较,逆序则交换,经过 n-1 趟后序列有序
时间复杂度$O(n)$、$O(n^2)$
稳定性稳定
难度⭐⭐

核心概念

冒泡排序(Bubble Sort)是最基础的交换排序算法,其核心思想是:相邻两个元素两两比较,如果逆序则交换,经过 n-1 趟排序后整个序列有序。

"冒泡"的含义:每一趟排序都会将关键字最小(或最大)的元素像气泡一样"浮"到序列的一端。

关键特性

  1. 相邻元素两两比较(区别于选择排序)
  2. 每一趟排序能保证一个元素到达最终位置(区别于插入排序)
  3. 冒泡排序产生的有序子序列是全局有序
  4. 稳定的排序算法
  5. 可以用于链表排序
与插入排序的区别冒泡的有序子序列是全局有序的(所有有序元素关键字都小于 / 大于未排序元素);插入排序只是局部有序

算法步骤

基本冒泡排序

  1. 从序列的最后一个元素开始,依次比较相邻的两个元素
  2. 如果 A[j-1] > A[j](逆序),则交换这两个元素
  3. 从后往前比较,直到比较到序列开头,一趟冒泡排序完成
  4. 一趟排序后,最小的元素到达序列的最前面
  5. 重复步骤 1–4,共执行 n-1 趟

优化版本

优化一:提前终止——设置标志位 flag,若某趟没有交换说明已有序,提前结束。

优化二:记录最后交换位置——记录每趟最后一次交换位置 last,下一趟只需比较到 last

代码实现

基本冒泡排序

void BubbleSort(int A[], int n) {
    int i, j, temp;
    for (i = 0; i < n - 1; i++) {  // 共 n-1 趟
        for (j = n - 1; j > i; j--) {  // 从后往前
            if (A[j-1] > A[j]) {  // 逆序则交换
                temp = A[j-1];
                A[j-1] = A[j];
                A[j] = temp;
            }
        }
    }
}

优化版本一:提前终止

void BubbleSort_Opt1(int A[], int n) {
    int i, j, temp;
    int flag;  // 标志位
    for (i = 0; i < n - 1; i++) {
        flag = 0;  // 每趟开始时置 0
        for (j = n - 1; j > i; j--) {
            if (A[j-1] > A[j]) {
                temp = A[j-1];
                A[j-1] = A[j];
                A[j] = temp;
                flag = 1;  // 发生交换,置 1
            }
        }
        if (flag == 0)  // 没有交换,已经有序
            return;
    }
}

优化版本二:记录最后交换位置

void BubbleSort_Opt2(int A[], int n) {
    int j, temp;
    int last = n - 1;  // 最后交换的位置
    while (last > 0) {
        int newLast = 0;
        for (j = n - 1; j > last - 1; j--) {
            if (A[j-1] > A[j]) {
                temp = A[j-1];
                A[j-1] = A[j];
                A[j] = temp;
                newLast = j;  // 更新最后交换位置
            }
        }
        last = newLast;  // 下一趟只需比较到 newLast
    }
}

手算示例

对序列 {49, 38, 65, 97, 76, 13, 27, 49*} 进行冒泡排序(从小到大):

初始:    49  38  65  97  76  13  27  49*

第1趟:   从后往前两两比较交换
         49  38  65  97  76  13  27  49*   ← 比较 27<49*,不交换
                              ↑比较 13<27,不交换
                      ↑比较 76>13,交换 → 49  38  65  97  13  76  27  49*
              ↑比较 97>13,交换 → 49  38  65  13  97  76  27  49*
          ↑比较 65>13,交换 → 49  38  13  65  97  76  27  49*
      ↑比较 38>13,交换 → 49  13  38  65  97  76  27  49*
  ↑比较 49>13,交换 → [13] 49  38  65  97  76  27  49*
         最小值 13 到达最终位置 ✓

第2趟:   → [13  27] 49  38  65  97  76  49*
第3趟:   → [13  27  38] 49  65  97  76  49*
第4趟:   → [13  27  38  49] 65  97  76  49*
第5趟:   → [13  27  38  49  49*] 65  97  76   (49 == 49* 不交换,保持稳定性)
第6趟:   → [13  27  38  49  49*  65] 76  97
第7趟:   → [13  27  38  49  49*  65  76  97]

最终结果:13, 27, 38, 49, 49*, 65, 76, 97(稳定!)

时间复杂度分析

情况时间复杂度说明
最好情况$O(n)$序列有序,只需比较 n-1 次
平均情况$O(n^2)$随机输入
最坏情况$O(n^2)$序列逆序
  • 最好情况(序列有序):比较次数 = n-1,交换次数 = 0;优化版本一趟即可结束 → $O(n)$。
  • 最坏情况(序列逆序):比较次数 $= \sum_{i=1}^{n-1} i = n(n-1)/2$,交换次数 = n(n-1)/2 → $O(n^2)$。
  • 平均情况:$O(n^2)$。

空间复杂度

空间复杂度为 $O(1)$:只需要一个临时变量用于交换,是原地排序算法。

稳定性分析

稳定冒泡排序是稳定的。当两个相邻元素相等时(A[j-1] == A[j]),比较条件 A[j-1] > A[j] 不满足,不进行交换,相等元素的相对顺序不变。

常见考法

考法 1:过程模拟写出每一趟排序后的结果。
考法 2:稳定性稳定的。
考法 3:与插入排序的区别冒泡全局有序,插入局部有序。
考法 4:优化提前终止、记录最后交换位置。
考法 5:比较次数最好 n-1,最坏 n(n-1)/2。
考法 6:能否用于链表可以。

易错点

注意
  1. ❌ 认为冒泡排序是不稳定的 → 稳定的
  2. ❌ 混淆冒泡排序和快速排序 → 冒泡是相邻比较,快排是分区划分。
  3. ❌ 认为比较次数始终是 n(n-1)/2 → 最好情况只有 n-1 次
  4. ❌ 忘记冒泡的有序子序列是全局有序的 → 这是冒泡的独特性质。
  5. ❌ 认为冒泡不适合链表 → 适合

核心结论

  1. 冒泡排序是稳定的。
  2. 最好 $O(n)$,平均和最坏 $O(n^2)$
  3. 空间复杂度为 $O(1)$
  4. 有序子序列是全局有序的。
  5. 每趟排序保证一个元素到达最终位置
  6. 相邻元素两两比较交换。
  7. 可以用于链表排序。
  8. 优化后最好情况只需 n-1 次比较

记忆卡片

冒泡排序为什么是稳定的?
相邻元素相等时不交换($A[j-1] > A[j]$ 不满足),相对顺序不变。
有序子序列有什么特点?
全局有序(有序元素均小于 / 大于未排序元素),与插入排序不同。
最好时间复杂度?何时达到?
$O(n)$;序列本身有序时一趟无交换,提前结束。
和快速排序的区别?
冒泡相邻比较每趟定一个最值;快排分区每趟定一个枢轴。冒泡 $O(n^2)$,快排平均 $O(n\log_2 n)$。
如何优化?
① 标志位无交换则提前结束;② 记录最后交换位置,下一趟只比到该处。
适用于链表吗?
适合,链表也能实现相邻比较交换。

交互动画 · 第 1 趟冒泡(最小值上浮)

数组 [49, 38, 65, 97, 76, 13, 27, 49],从后往前两两比较,最小值上浮 490 381 652 973 764 135 276 497 橙格 = 当前正在比较的两个相邻元素;交换后右侧较小值左移,最终 13 浮到最前
点击「播放第 1 趟」查看从后往前两两比较、最小值上浮的过程
共 7 次比较,5 次交换(13 上浮到位置 0)

相关知识点

(暂无关联知识点)