cv.cm

编码 · 算法

冒泡排序详解:原理、代码与时间复杂度

阅读约 5 分钟

简短回答

冒泡排序是一种简单的排序算法:反复遍历列表,交换每一对顺序错误的相邻元素。每一趟之后,剩余元素中最大的那个会移动(像气泡一样浮起)到末尾的最终位置。它在平均和最坏情况下需要 O(n²) 时间,对已经有序的列表是 O(n),使用 O(1) 额外空间,并且是稳定的。它主要用于教学;实际程序使用内置的排序函数。

打开行排序

通俗地说,思路是什么

比较前两个元素,如果第一个更大就交换。然后比较第二个和第三个,再比较第三个和第四个,依此类推直到列表末尾。这就是一趟。第一趟结束后,最大的元素一定在最后,因为每次比较都把它向右带了一步。

对未排序的部分重复这一趟,每次未排序部分缩短一个元素。如果某一趟没有发生任何交换,说明列表已经有序,可以提前停止。

逐趟演示

将 [5, 1, 4, 2, 8] 按升序排序。竖线之后的元素已在最终位置。

趟数比较与交换该趟之后的列表
15↔1 交换,5↔4 交换,5↔2 交换,5 与 8 保持不变[1, 4, 2, 5, 8] (8 已就位)
21 与 4 保持,4↔2 交换,4 与 5 保持[1, 2, 4, 5, 8] (5 已就位)
31 与 2 保持,2 与 4 保持,没有交换[1, 2, 4, 5, 8] 已有序,停止

如果没有提前退出,算法还会再跑第四趟;有了提前退出,这里三趟就够了。

Python 实现

def bubble_sort(items):
    """Sort a list in place, ascending. Returns the same list."""
    n = len(items)
    for end in range(n - 1, 0, -1):     # last index of the unsorted part
        swapped = False
        for i in range(end):
            if items[i] > items[i + 1]:  # strict >, keeps equal items in order
                items[i], items[i + 1] = items[i + 1], items[i]
                swapped = True
        if not swapped:                  # no swaps: already sorted
            break
    return items
print(bubble_sort([5, 1, 4, 2, 8]))     # [1, 2, 4, 5, 8]
print(bubble_sort([3, 3, 1]))           # [1, 3, 3]
print(bubble_sort([]))                  # []

内层循环在 end 处停止,因为它之后的元素都已就位。swapped 标志使有序输入下达到 O(n) 的最好情况。

时间与空间复杂度

情况比较次数时间原因
最好(已有序)n − 1O(n)一趟没有交换,然后停止
平均(随机顺序)约 n²/2O(n²)平均约 n²/4 次交换
最坏(逆序)n(n − 1)/2O(n²)每次比较都要交换
额外空间O(1)原地排序,只用几个变量

对于逆序的 10,000 个元素,这将近有 5000 万次比较和交换。交换次数始终等于逆序对的数量,所谓逆序对,就是顺序颠倒的元素对。

冒泡排序与其他排序算法的比较

算法最好平均最坏额外空间稳定
冒泡排序O(n)O(n²)O(n²)O(1)是
选择排序O(n²)O(n²)O(n²)O(1)否
插入排序O(n)O(n²)O(n²)O(1)是
归并排序O(n log n)O(n log n)O(n log n)O(n)是
快速排序O(n log n)O(n log n)O(n²)O(log n)否
堆排序O(n log n)O(n log n)O(n log n)O(1)否
Timsort(Python 的 sorted)O(n)O(n log n)O(n log n)O(n)是

冒泡排序是稳定的,因为它只交换相邻元素,并且只在严格逆序时才交换,所以相等的元素永远不会越过彼此。插入排序的复杂度和稳定性与它相同,但在实践中写入次数更少、速度更快,因此像 Timsort 这样的混合算法内部用的是插入排序,而不是冒泡排序。

变体与常见错误

  • 鸡尾酒排序在交替的趟中先从左到右、再从右到左,能更快地把末尾的小元素(俗称「乌龟」)移到前面。它仍然是 O(n²)。
  • 梳排序比较相隔一定间距的元素,并逐步缩小间距,能提早消除大量「乌龟」;间距为 1 时就退化成冒泡排序。
  • 奇偶排序先比较所有奇数对,再比较所有偶数对,常用于讲解并行排序。

何时使用冒泡排序

在生产代码中几乎不用。在 Python 中,请调用 sorted(items) 或 items.sort(),它们使用 Timsort,时间 O(n log n),并且是稳定的。冒泡排序仍然值得了解:它是最容易讲清楚的排序,正确性证明也很好懂,而且是考察循环、交换和复杂度时常见的入门面试题或考试题。

它唯一的实际优势是只需一趟 O(n) 就能检测出列表已有序,但只比较相邻元素的普通循环无需排序也能做到这一点。

常见问题

冒泡排序稳定吗?

稳定。它只在左边元素严格更大时才交换相邻元素,所以相等的元素保持原有顺序。

冒泡排序的时间复杂度是多少?

平均和最坏情况下是 O(n²)。如果某一趟没有交换就提前退出,已有序输入下的最好情况是 O(n)。

为什么叫冒泡排序?

大的元素每次一个交换地向列表末尾移动,就像气泡浮向水面。第 k 趟之后,最大的 k 个元素已就位。

冒泡排序比插入排序快吗?

不快。两者都是 O(n²),但插入排序通常操作更少,实际运行更快,对接近有序的数据尤其如此。

冒泡排序会用额外内存吗?

不会。它原地排序,只需要 O(1) 的额外空间来存放循环下标和交换时的临时值。

全部编码教程