冒泡排序 (Bubble Sort)

raw/articles/2026/05/冒泡排序

一句话总结

冒泡排序是对选择排序的优化,通过交换相邻逆序对完成排序,是一种稳定排序算法,支持在数组有序时提前终止。

核心要点

1. 从选择排序的缺陷出发

选择排序存在三个主要问题:

  • 不稳定排序:交换操作会改变相同元素的相对位置
  • 无视数组有序度:即使输入已有序,时间复杂度仍是 O(n²)
  • 时间复杂度无法优化:固定执行约 n²/2 次操作

冒泡排序通过逐步优化选择排序来解决这些问题。

2. 三个优化阶段

阶段一:获得稳定性

  • 将选择排序的"交换"改为"数据搬移"
  • nums[sortedIndex..minIndex] 整体后移一位,让最小值插入正确位置
  • 代价:增加了一个 for 循环,实际执行次数增加

阶段二:合并操作(真正的冒泡排序)

  • 倒序遍历,发现逆序对就交换相邻元素
  • 最小值会像气泡一样逐步"冒"到正确位置
  • 消除了额外的数据搬移循环,执行次数回到约 n²/2 次
  • 关键:只交换相邻元素,不改变相同元素的相对位置 → 稳定排序

阶段三:提前终止

  • 添加 swapped 布尔变量记录是否发生交换
  • 如果一轮遍历没有交换,说明数组已有序,提前退出
  • 对近乎有序的数组效率显著提升

基本原理

冒泡排序的核心思想是通过交换相邻逆序对,让最小值逐步移动到正确位置

初始: [5, 3, 1, 4, 2]
第1轮 (sortedIndex=0):
  i=4: [5,3,1,4,2] → [5,3,1,2,4]  (交换4,2)
  i=3: [5,3,1,2,4] → [5,3,1,2,4]  (1<2,不交换)
  i=2: [5,3,1,2,4] → [5,3,1,2,4]  (3>1,不交换)
  i=1: [5,3,1,2,4] → [3,5,1,2,4]  (5>3,交换)
  结果: [3,5,1,2,4] → 最小值1已冒泡到位置0? 不对...

实际上代码是倒序遍历,让最小值逐步往前移动:

// 冒泡排序完整版(含提前终止)
void sort(int[] nums) {
    int n = nums.length;
    int sortedIndex = 0;
    while (sortedIndex < n) {
        boolean swapped = false;
        // 倒序遍历,交换逆序对
        for (int i = n - 1; i > sortedIndex; i--) {
            if (nums[i] < nums[i - 1]) {
                int tmp = nums[i];
                nums[i] = nums[i - 1];
                nums[i - 1] = tmp;
                swapped = true;
            }
        }
        if (!swapped) break;  // 提前终止
        sortedIndex++;
    }
}
特性 说明
核心操作 交换相邻逆序对
稳定性 稳定(只交换相邻元素)
提前终止 支持(数组有序时 O(n))
排序方向 每次将最小值"冒"到 sortedIndex 位置

时间复杂度

场景 时间复杂度 说明
最坏情况 O(n²) 数组完全逆序
平均情况 O(n²) 约 n²/2 次比较和交换
最好情况 O(n) 数组已有序,一轮遍历后提前终止
空间复杂度 O(1) 原地排序,仅用常数额外空间

代码实现

关键代码片段

// 基础冒泡排序(无提前终止)
void sort(int[] nums) {
    int n = nums.length;
    int sortedIndex = 0;
    while (sortedIndex < n) {
        // 倒序遍历,交换逆序对
        for (int i = n - 1; i > sortedIndex; i--) {
            if (nums[i] < nums[i - 1]) {
                int tmp = nums[i];
                nums[i] = nums[i - 1];
                nums[i - 1] = tmp;
            }
        }
        sortedIndex++;
    }
}
// 优化版:提前终止
void sort(int[] nums) {
    int n = nums.length;
    int sortedIndex = 0;
    while (sortedIndex < n) {
        boolean swapped = false;
        for (int i = n - 1; i > sortedIndex; i--) {
            if (nums[i] < nums[i - 1]) {
                int tmp = nums[i];
                nums[i] = nums[i - 1];
                nums[i - 1] = tmp;
                swapped = true;
            }
        }
        if (!swapped) break;
        sortedIndex++;
    }
}

实现要点

  • 倒序遍历:从数组末尾向 sortedIndex 遍历,让最小值逐步前移
  • 相邻交换:只交换 nums[i]nums[i-1],保证稳定性
  • 提前终止标志:用 swapped 变量记录本轮是否发生交换

对比分析

维度 冒泡排序 选择排序
时间复杂度(最坏) O(n²) O(n²)
时间复杂度(最好) O(n)(提前终止) O(n²)(无法优化)
稳定性 稳定 不稳定
交换次数 较多(每次逆序都交换) 较少(每轮只交换一次)
适用场景 近乎有序的数组 不关心稳定性时

应用场景

  • 教学演示:最简单的稳定排序算法之一
  • 小规模数据排序
  • 近乎有序的数组(提前终止优化生效)
  • 需要稳定排序且对时间要求不严格的场景

验证方法

  • 可通过 LeetCode 912「排序数组」验证(会超时,但验证正确性)
  • 测试稳定排序:用 (value, originalIndex) 元组验证相同值相对位置不变

相关题目

平台 题目 难度
LeetCode 912. 排序数组 中等

相关概念

  • 排序算法:排序算法的评估指标与分类
  • 选择排序-总结:冒泡排序的优化源头
  • 时间复杂度:O(n²) 的含义
  • 稳定排序:相同元素排序后相对位置不变的特性
  • 逆序对:相邻元素逆序是冒泡排序的交换依据

相关实体

总结

冒泡排序是对选择排序的优化,通过交换相邻逆序对实现稳定排序,并支持提前终止来利用数组的有序度。虽然时间复杂度仍是 O(n²),但在近乎有序的场景下表现更好。


Interactive Graph