← 学习中心

插入、选择、冒泡排序:三种 O(n²) 入门排序为何仍值得学

插入、选择、冒泡是三种最朴素的平方级排序,却是理解"算法设计"与"复杂度"的最佳起点。本文讲清它们的思想、代码、稳定性与适用场景,并说明为何现代语言仍把它们当作小数组的收尾算法。

本节你将能

学完这一节,你将能说清三件事:插入、选择、冒泡三种排序分别怎么排、它们的好/坏情况与稳定性有何差异、以及为什么看似"慢"的 O(n²) 排序今天仍无处不在。这是整个排序模块(B1–B7)的入口,后续归并、快排、堆排序都会回头比较它们。

一、为什么要先学"慢"的排序

很多人疑惑:既然有 O(n log n) 的快排、归并,为什么还要学 O(n²) 的入门三剑客?答案有两点。第一,它们思想极简、易于证明正确,是理解loop invariant(循环不变量)和复杂度分析的最佳练兵场;第二,它们的常数因子极小、且是原地排序,在对小数组或近乎有序的数据上,反而比复杂算法更快。现代标准库(如 C++ introsort、Python Timsort)都会在子数组足够小时切换回插入排序。

二、插入排序:像整理手牌一样

插入排序的核心类比是你打扑克:手里已排好序的牌,每摸到一张新牌,就从右往左找到它的位置插进去。算法对数组从第 2 个元素开始,把当前元素当作"key",向左扫描并把比它大的元素逐个右移,最后空出的位置放上 key。

代码(Python):

def insertion_sort(a):
    for i in range(1, len(a)):
        key = a[i]
        j = i - 1
        while j >= 0 and a[j] > key:
            a[j + 1] = a[j]
            j -= 1
        a[j + 1] = key
    return a

复杂度:最坏(逆序)与平均都是 O(n²),但最好情况(已升序)只需 O(n)——内层循环一次都不执行。它稳定,且原地(只需 O(1) 额外空间)。移动次数恰好等于数组的逆序对数量。

插入排序:把当前元素插进左侧已排好序的部分第1趟524613已排好keykey=2 比 5 大? 向左找位置归位后254613绿色 = 已有序前缀;琥珀 = 正在归位的 key

图:插入排序在 [5,2,4,6,1,3] 上的第 1 趟归位过程

三、选择排序:每趟挑最小的放前面

选择排序的思想是"先挑最小的放最前":在尚未排序的部分里找到最小元素,与未排序区的第一个位置交换;重复直到结束。它和插入排序的区别在于——插入是"把当前元素归位",选择是"把当前位置该有的元素换过来"。

代码:

def selection_sort(a):
    n = len(a)
    for i in range(n):
        m = i
        for j in range(i + 1, n):
            if a[j] < a[m]:
                m = j
        a[i], a[m] = a[m], a[i]
    return a

复杂度:无论输入如何,比较次数恒为 n(n-1)/2,始终是 O(n²);交换次数最多 n-1 次(比插入排序少很多)。但它是不稳定的——例如 [2ₐ, 2_b, 1],第一轮把 1 换到最前,2ₐ 被推到 2_b 之后,相等元素的相对顺序被破坏。它同样原地、O(1) 空间。

选择排序:每趟在未排序区找最小值,换到前端选最小前524613最小值 1在 [5,2,4,6,1,3] 中扫到最小 1交换后124653最小值就位,前缀有序

图:选择排序第 1 趟——最小值 1 换到最前

四、冒泡排序:相邻比较,大的往右冒

冒泡排序反复扫描数组,比较相邻两个元素,若顺序不对就交换,于是每趟都会把当前未排序区里最大的元素"冒泡"到末尾。加上"某趟没有发生交换就提前结束"的优化后,最好情况也可达 O(n)。

代码:

def bubble_sort(a):
    n = len(a)
    for i in range(n):
        swapped = False
        for j in range(n - 1 - i):
            if a[j] > a[j + 1]:
                a[j], a[j + 1] = a[j + 1], a[j]
                swapped = True
        if not swapped:
            break
    return a

它稳定、原地,但比较和交换都偏多,实践中最慢,主要作为教学示例与稳定性演示存在。

冒泡排序:每趟把当前未排序区的最大值冒到末尾一趟前524613相邻比较,顺序不对就交换一趟后245136本趟最大 6 已就位

图:冒泡排序第 1 趟——最大值 6 冒泡到末尾

五、三者对比

排序最坏最好稳定性交换次数特点
插入排序O(n²)O(n)稳定≤逆序对近乎有序时极快,适合在线数据
选择排序O(n²)O(n²)不稳定≤ n-1交换少,但与输入无关地慢
冒泡排序O(n²)O(n)*稳定较多教学用,演示稳定性

*含"无交换提前退出"优化时。

六、稳定性与适用场景

稳定性指相等元素的相对顺序在排序后保持不变。插入排序和冒泡排序稳定,选择排序不稳定。稳定性在多关键字排序中很关键:例如先按"分数"排、再按"姓名"排,若第二次排序不稳定,第一次的分数顺序就会被打乱。数据库与标准库的排序都格外看重这一点(Timsort 就是稳定排序)。

小结与下一步

O(n²) 三剑客是排序世界的"地基":插入排序胜在近乎有序与小数组,选择排序交换最少,冒泡排序用于讲清稳定性。下一步建议推进:B2 归并排序(用分治把复杂度压到 O(n log n))→ B4 堆排序(用堆结构原地 O(n log n))→ B3 快速排序(平均最快,昨日 620 已铺垫)。整个排序轨道已在缺口体系中排好优先级,逐个击破即可。

---
插入、选择、冒泡排序:三种 O(n²) 入门排序为何仍值得学 | 必学必会