【算法图解】冒泡排序:最容易理解的排序

冒泡排序很慢,但它是理解“比较排序”与“交换”的最佳起点。本文用逐轮冒泡过程、边界优化、稳定性分析与可运行代码,让你真正理解它为什么是 O(n²)。

10 分钟阅读
小明

【算法图解】冒泡排序:最容易理解的排序

冒泡排序(Bubble Sort)常被吐槽:

  • 过时
  • 面试只会问“你知道它很慢吗”

但它仍然值得学。

原因很简单:它是最容易把“比较排序”讲清楚的算法

学会冒泡,你会更容易理解:

  • 交换排序的思路
  • “稳定性”到底是什么意思
  • 为什么 $O(n^2)$ 在数据量大时会崩

1. 冒泡的直觉:大的往后沉

把数组想成一排人,冒泡排序每次只做一件事:

  • 从左到右看相邻两个人
  • 如果顺序不对就交换

这样一轮走完,最大的元素就会被“冒”到最右边。


2. 最基础实现(可运行)

function bubbleSort(arr) {
  const a = arr.slice()
  const n = a.length

  for (let i = 0; i < n - 1; i++) {
    for (let j = 0; j < n - 1 - i; j++) {
      if (a[j] > a[j + 1]) {
        const t = a[j]
        a[j] = a[j + 1]
        a[j + 1] = t
      }
    }
  }

  return a
}

console.log(bubbleSort([5, 1, 4, 2, 8]))

两层循环是它慢的根源。


3. 为什么复杂度是 O(n²)?

  • 外层循环跑 $n-1$ 轮
  • 第 1 轮比较 $n-1$ 次
  • 第 2 轮比较 $n-2$ 次
  • ...

总比较次数大约是:

$$ (n-1) + (n-2) + \cdots + 1 = \frac{n(n-1)}{2} $$

所以时间复杂度是 $O(n^2)$。

空间复杂度是 $O(1)$(原地交换)。


4. 重要性质:稳定性

“稳定排序”指:

  • 如果两个元素相等
  • 排序后它们的相对顺序不变

冒泡排序是稳定的(因为它只在 > 时交换,不会把相等元素打乱)。

稳定性在工程里很重要:例如按“主键排序”后再按“次键排序”。


5. 一个小优化:如果一轮没交换,直接结束

很多时候数组本来就接近有序,没必要跑满 $n$ 轮。

function bubbleSortOptimized(arr) {
  const a = arr.slice()
  const n = a.length

  for (let i = 0; i < n - 1; i++) {
    let swapped = false

    for (let j = 0; j < n - 1 - i; j++) {
      if (a[j] > a[j + 1]) {
        const t = a[j]
        a[j] = a[j + 1]
        a[j + 1] = t
        swapped = true
      }
    }

    if (!swapped) break
  }

  return a
}

这个优化能把“接近有序”的情况变得更快。


6. 面试怎么答?

你可以这样说:

  • 冒泡排序通过相邻比较与交换,让最大元素每轮冒到末尾。
  • 时间复杂度平均/最坏 $O(n^2)$,最好(已排序 + 早停)可到 $O(n)$。
  • 空间复杂度 $O(1)$,稳定排序。

总结

  • 冒泡排序是理解排序的最好起点。
  • 慢是因为两层循环带来的 $O(n^2)$。
  • 它稳定、原地,但不适合大规模数据。