引言

排序是计算机科学中最基础也最重要的操作之一。无论是数据库的 ORDER BY、搜索引擎的结果排序,还是操作系统的进程调度,都离不开排序算法。

冒泡排序(Bubble Sort)是最简单直观的排序算法,也是大多数人接触的第一个排序算法。虽然它的性能较差(O(n²)),但它的思想——通过相邻元素的比较和交换,像气泡一样把最大(或最小)的元素"浮"到顶端——是理解更复杂排序算法的基石。

本文将彻底拆解冒泡排序:从基本思想到代码实现,从普通版本到优化版本,从算法分析到面试陷阱。

第一部分:算法思想

一、核心原理

冒泡排序的核心思想可以概括为:

反复遍历待排序序列,每次比较相邻两个元素,如果顺序不对就交换,直到整个序列有序。

就像水中的气泡,较轻的气泡会逐渐上浮到水面。冒泡排序中,较小的元素会像气泡一样慢慢"浮"到数组的前面(或较大的元素"沉"到后面)。

二、一轮冒泡的过程

三、完整排序过程

规律总结:

  • 第 k 轮(k 从 0 开始),需要比较 n - k - 1 次

  • 第 k 轮结束后,数组末尾的 k+1 个元素已经有序

  • 总共需要 n-1 轮


第二部分:代码实现

一、基础版本(无优化)

#include <stdio.h>

void bubbleSort_basic(int arr[], int len) {
    // 外层循环:控制轮数,共 len-1 轮
    for (int i = 0; i < len - 1; i++) {
        // 内层循环:每轮比较相邻元素
        // 每轮结束后最后 i+1 个元素已有序,不需要再比较
        for (int j = 0; j < len - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                // 交换
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
        }
    }
}

二、优化版本(提前终止)

核心问题:如果某一轮比较中没有发生任何交换,说明数组已经有序,可以提前结束。

优化思路:用一个标志位 tag 记录本轮是否发生了交换。

#include <stdio.h>
#include <stdbool.h>

void bubbleSort_optimized(int arr[], int len) {
    for (int i = 0; i < len - 1; i++) {
        bool tag = true;  // 假设本轮已经有序
        
        for (int j = 0; j < len - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                // 发生交换 → 说明还未有序
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
                tag = false;
            }
        }
        
        // 本轮没有发生交换 → 已经完全有序,直接退出
        if (tag) {
            break;
        }
    }
}

优化效果分析:

三、进一步优化(记录最后交换位置)

void bubbleSort_final(int arr[], int len) {
    int lastSwap = len - 1;  // 记录最后一轮交换的位置
    
    for (int i = 0; i < len - 1; i++) {
        int currentSwap = 0;  // 本轮最后交换的位置
        bool swapped = false;
        
        for (int j = 0; j < lastSwap; j++) {
            if (arr[j] > arr[j + 1]) {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
                swapped = true;
                currentSwap = j;  // 记录交换位置
            }
        }
        
        if (!swapped) break;
        lastSwap = currentSwap;  // 下一轮只比较到这里
    }
}

原理:某轮中最后一次交换的位置之后,所有元素已经有序,下一轮不需要再比较。


第三部分:完整测试代码

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>

void bubbleSort(int arr[], int len) {
    for (int i = 0; i < len - 1; i++) {
        bool tag = true;
        for (int j = 0; j < len - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
                tag = false;
            }
        }
        if (tag) break;
    }
}

void printArray(int arr[], int len, const char* msg) {
    printf("%s", msg);
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    // 测试1:普通数组
    int arr1[] = {5, 3, 8, 1, 2, 7, 6, 4};
    int len1 = sizeof(arr1) / sizeof(arr1[0]);
    printArray(arr1, len1, "排序前:");
    bubbleSort(arr1, len1);
    printArray(arr1, len1, "排序后:");
    
    // 测试2:已经有序的数组(测试优化效果)
    int arr2[] = {1, 2, 3, 4, 5, 6, 7, 8};
    int len2 = sizeof(arr2) / sizeof(arr2[0]);
    printf("\n已有序数组测试:\n");
    printArray(arr2, len2, "排序前:");
    bubbleSort(arr2, len2);
    printArray(arr2, len2, "排序后:");
    printf("只执行一轮就退出(优化生效)\n");
    
    // 测试3:逆序数组(最坏情况)
    int arr3[] = {8, 7, 6, 5, 4, 3, 2, 1};
    int len3 = sizeof(arr3) / sizeof(arr3[0]);
    printf("\n逆序数组测试:\n");
    printArray(arr3, len3, "排序前:");
    bubbleSort(arr3, len3);
    printArray(arr3, len3, "排序后:");
    
    // 测试4:有重复元素
    int arr4[] = {3, 1, 4, 1, 5, 9, 2, 6, 5};
    int len4 = sizeof(arr4) / sizeof(arr4[0]);
    printf("\n重复元素测试:\n");
    printArray(arr4, len4, "排序前:");
    bubbleSort(arr4, len4);
    printArray(arr4, len4, "排序后:");
    
    return 0;
}

第四部分:算法分析

一、时间复杂度

情况时间复杂度说明
最好O(n)数组已有序,优化版一轮结束
最坏O(n²)数组逆序,需 n-1 轮
平均O(n²)一般情况

推导过程:

二、空间复杂度

O(1),只需要一个临时变量 tmp 用于交换。

三、稳定性

冒泡排序是稳定的。

原因:当相邻两个元素相等时,不会交换它们的位置。

if (arr[j] > arr[j + 1]) {  // 注意是 >,不是 >=
    // arr[j] == arr[j+1] 时不会进入这里
    swap(arr[j], arr[j+1]);
}

第五部分:与其他基础排序的对比

对比项冒泡排序选择排序插入排序
最好时间O(n)O(n²)O(n)
最坏时间O(n²)O(n²)O(n²)
平均时间O(n²)O(n²)O(n²)
空间O(1)O(1)O(1)
稳定性✅ 稳定❌ 不稳定✅ 稳定
交换次数最多 O(n²)最多 O(n)最多 O(n²)

第六部分:面试考点整合

1. Q:冒泡排序的最好时间复杂度是多少?

A:O(n)。当数组已经有序时,优化版冒泡排序第一轮比较 n-1 次后没有发生交换,直接退出。没有优化的版本仍然是 O(n²)。

2. Q:冒泡排序是稳定的吗?为什么?

A:稳定。因为只有在 arr[j] > arr[j+1] 时才交换,相等的元素不会交换,保持了原有的相对顺序。

3. Q:冒泡排序和选择排序有什么区别?

A:冒泡是相邻比较交换,选择是选最小的和当前位置交换。冒泡交换次数多(O(n²)),选择交换次数少(O(n))。冒泡是稳定的,选择是不稳定的。

4. Q:如何在冒泡排序中实现降序排序?

A:将判断条件从 arr[j] > arr[j+1] 改为 arr[j] < arr[j+1]。

// 降序冒泡
if (arr[j] < arr[j + 1]) {
    // 交换,大的放前面
}

5. Q:冒泡排序在实际工程中还会使用吗?

A:几乎不会。对于小数据集,插入排序性能更好;对于大数据集,快速排序或归并排序更优。冒泡排序主要价值在于教学——它是最容易理解的排序算法。


总结

一、核心要点

要点内容
算法思想相邻比较交换,最大元素逐轮"沉底"
时间复杂度最好 O(n),最坏 O(n²),平均 O(n²)
空间复杂度O(1)
稳定性✅ 稳定(相等不交换)
核心优化添加 tag 标志位,无交换时提前终止

二、优化版本记忆

// 冒泡排序核心框架(带优化)
for (i = 0; i < n-1; i++) {
    tag = true;              // 假设已有序
    for (j = 0; j < n-i-1; j++) {
        if (arr[j] > arr[j+1]) {
            swap(j, j+1);
            tag = false;     // 发生了交换
        }
    }
    if (tag) break;          // 无交换 → 已有序
}

三、一句话记忆

冒泡排序通过 n-1 轮相邻比较交换,让最大元素像气泡一样每轮"浮"到末尾。加个 tag 标志位检测提前有序,最好情况可优化到 O(n),是稳定的原地排序算法。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐