【数据结构与算法】第32篇:交换排序(一):冒泡排序

一、冒泡排序的基本思想

1.1 算法原理

重复遍历数组,每次比较相邻两个元素,如果顺序错误就交换。每一趟遍历都会把当前未排序部分的最大元素"冒泡"到最后。

步骤

  1. 比较相邻元素,如果前一个比后一个大,交换

  2. 对每一对相邻元素重复比较

  3. 每趟结束后,最后一个元素就是当前最大值

  4. 重复以上步骤,直到没有需要交换的元素

1.2 图解示例

初始:[5, 1, 4, 2, 8]

text

复制代码
第1趟:
比较5和1 → 交换 → [1, 5, 4, 2, 8]
比较5和4 → 交换 → [1, 4, 5, 2, 8]
比较5和2 → 交换 → [1, 4, 2, 5, 8]
比较5和8 → 不交换 → [1, 4, 2, 5, 8]
第1趟结束,最大值8已在末尾

第2趟:
比较1和4 → 不交换 → [1, 4, 2, 5, 8]
比较4和2 → 交换 → [1, 2, 4, 5, 8]
比较4和5 → 不交换
第2趟结束

第3趟:
比较1和2 → 不交换
比较2和4 → 不交换
无交换,排序完成

二、基础实现

c

复制代码
#include <stdio.h>

void bubbleSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                // 交换
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

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

int main() {
    int arr[] = {5, 1, 4, 2, 8};
    int n = sizeof(arr) / sizeof(arr[0]);
    
    printf("原数组: ");
    printArray(arr, n);
    
    bubbleSort(arr, n);
    
    printf("排序后: ");
    printArray(arr, n);
    
    return 0;
}

运行结果:

text

复制代码
原数组: 5 1 4 2 8 
排序后: 1 2 4 5 8 

三、优化一:提前终止

3.1 优化思路

如果某一趟遍历中没有发生任何交换,说明数组已经有序,可以提前结束排序。

3.2 代码实现

c

复制代码
void bubbleSortOptimized1(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int swapped = 0;  // 标记是否发生交换
        
        for (int j = 0; j < n - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                swapped = 1;
            }
        }
        
        // 如果没有交换,说明已有序
        if (swapped == 0) {
            printf("第%d趟无交换,提前结束\n", i + 1);
            break;
        }
        printf("第%d趟结束\n", i + 1);
    }
}

四、优化二:记录最后交换位置

4.1 优化思路

每趟遍历中,最后一次发生交换的位置之后的所有元素都已经有序。下一趟只需比较到这个位置即可。

4.2 代码实现

c

复制代码
void bubbleSortOptimized2(int arr[], int n) {
    int lastSwap = n - 1;  // 最后交换位置
    
    for (int i = 0; i < n - 1; i++) {
        int swapped = 0;
        int newLastSwap = 0;
        
        for (int j = 0; j < lastSwap; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                swapped = 1;
                newLastSwap = j;  // 记录最后交换位置
            }
        }
        
        if (swapped == 0) break;
        lastSwap = newLastSwap;  // 缩小下一趟范围
        printf("第%d趟结束,最后交换位置=%d\n", i + 1, lastSwap);
    }
}

五、完整代码(含两种优化)

c

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// 基础冒泡
void bubbleSortBasic(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

// 优化1:提前终止
void bubbleSortOptimized1(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int swapped = 0;
        for (int j = 0; j < n - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                swapped = 1;
            }
        }
        if (swapped == 0) break;
    }
}

// 优化2:记录最后交换位置
void bubbleSortOptimized2(int arr[], int n) {
    int lastSwap = n - 1;
    for (int i = 0; i < n - 1; i++) {
        int swapped = 0;
        int newLastSwap = 0;
        for (int j = 0; j < lastSwap; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                swapped = 1;
                newLastSwap = j;
            }
        }
        if (swapped == 0) break;
        lastSwap = newLastSwap;
    }
}

// 打印数组
void printArray(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

// 复制数组
void copyArray(int src[], int dst[], int n) {
    for (int i = 0; i < n; i++) {
        dst[i] = src[i];
    }
}

// 生成随机数组
void generateRandomArray(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        arr[i] = rand() % 1000;
    }
}

// 生成近乎有序数组
void generateNearlySortedArray(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        arr[i] = i;
    }
    // 交换少量元素
    for (int i = 0; i < n / 20; i++) {
        int p1 = rand() % n;
        int p2 = rand() % n;
        int temp = arr[p1];
        arr[p1] = arr[p2];
        arr[p2] = temp;
    }
}

int main() {
    srand(time(NULL));
    
    printf("=== 冒泡排序优化对比 ===\n\n");
    
    // 测试1:随机数组
    int n = 10000;
    int *arr1 = (int*)malloc(n * sizeof(int));
    int *arr2 = (int*)malloc(n * sizeof(int));
    int *arr3 = (int*)malloc(n * sizeof(int));
    
    generateRandomArray(arr1, n);
    copyArray(arr1, arr2, n);
    copyArray(arr1, arr3, n);
    
    clock_t start, end;
    double time1, time2, time3;
    
    start = clock();
    bubbleSortBasic(arr1, n);
    end = clock();
    time1 = (double)(end - start) / CLOCKS_PER_SEC * 1000;
    
    start = clock();
    bubbleSortOptimized1(arr2, n);
    end = clock();
    time2 = (double)(end - start) / CLOCKS_PER_SEC * 1000;
    
    start = clock();
    bubbleSortOptimized2(arr3, n);
    end = clock();
    time3 = (double)(end - start) / CLOCKS_PER_SEC * 1000;
    
    printf("随机数组 (n=%d):\n", n);
    printf("  基础冒泡:     %.2f ms\n", time1);
    printf("  优化1(提前终止): %.2f ms\n", time2);
    printf("  优化2(缩小范围): %.2f ms\n", time3);
    printf("  优化2 比 基础 快 %.2f 倍\n\n", time1 / time3);
    
    // 测试2:近乎有序数组
    generateNearlySortedArray(arr1, n);
    copyArray(arr1, arr2, n);
    copyArray(arr1, arr3, n);
    
    start = clock();
    bubbleSortBasic(arr1, n);
    end = clock();
    time1 = (double)(end - start) / CLOCKS_PER_SEC * 1000;
    
    start = clock();
    bubbleSortOptimized1(arr2, n);
    end = clock();
    time2 = (double)(end - start) / CLOCKS_PER_SEC * 1000;
    
    start = clock();
    bubbleSortOptimized2(arr3, n);
    end = clock();
    time3 = (double)(end - start) / CLOCKS_PER_SEC * 1000;
    
    printf("近乎有序数组 (n=%d):\n", n);
    printf("  基础冒泡:     %.2f ms\n", time1);
    printf("  优化1(提前终止): %.2f ms\n", time2);
    printf("  优化2(缩小范围): %.2f ms\n", time3);
    
    free(arr1);
    free(arr2);
    free(arr3);
    
    // 测试3:演示优化效果
    printf("\n=== 演示优化效果 ===\n");
    int demo[] = {5, 1, 2, 3, 4};
    int m = sizeof(demo) / sizeof(demo[0]);
    
    printf("原数组: ");
    printArray(demo, m);
    
    // 使用优化2,可以看到每趟结束后的状态
    int lastSwap = m - 1;
    for (int i = 0; i < m - 1; i++) {
        int swapped = 0;
        int newLastSwap = 0;
        for (int j = 0; j < lastSwap; j++) {
            if (demo[j] > demo[j + 1]) {
                int temp = demo[j];
                demo[j] = demo[j + 1];
                demo[j + 1] = temp;
                swapped = 1;
                newLastSwap = j;
            }
        }
        printf("第%d趟后: ", i + 1);
        printArray(demo, m);
        if (swapped == 0) {
            printf("未发生交换,提前结束\n");
            break;
        }
        lastSwap = newLastSwap;
        printf("下一趟只需比较到下标 %d\n", lastSwap);
    }
    
    return 0;
}

运行结果(示例):

text

复制代码
=== 冒泡排序优化对比 ===

随机数组 (n=10000):
  基础冒泡:     385.42 ms
  优化1(提前终止): 382.15 ms
  优化2(缩小范围): 368.33 ms
  优化2 比 基础 快 1.05 倍

近乎有序数组 (n=10000):
  基础冒泡:     195.67 ms
  优化1(提前终止): 0.23 ms
  优化2(缩小范围): 0.21 ms

=== 演示优化效果 ===
原数组: 5 1 2 3 4 
第1趟后: 1 2 3 4 5 
下一趟只需比较到下标 3
第2趟后: 1 2 3 4 5 
未发生交换,提前结束

六、复杂度分析

情况 比较次数 交换次数 时间复杂度
最好(已有序) n-1 0 O(n)
最坏(逆序) n(n-1)/2 n(n-1)/2 O(n²)
平均 n(n-1)/2 n(n-1)/4 O(n²)

空间复杂度 :O(1),原地排序
稳定性:稳定(相等时不交换)


七、冒泡排序的特点

优点 缺点
实现简单,容易理解 时间复杂度高 O(n²)
空间复杂度低 O(1) 大规模数据效率低
稳定排序 -
可以提前终止,在有序时效率高 -
原地排序,不需要额外空间 -

适用场景

  • 数据规模很小(n < 1000)

  • 数据基本有序

  • 教学演示,理解排序思想


八、小结

这一篇我们学习了冒泡排序及其优化:

版本 优化点 效果
基础冒泡 始终执行 n-1 趟
优化1 无交换则提前终止 有序时 O(n)
优化2 记录最后交换位置 减少每趟比较范围

核心要点

  • 每趟把最大元素"冒泡"到末尾

  • 优化1:某趟无交换 → 已有序

  • 优化2:最后交换位置之后已有序

下一篇我们讲快速排序。


九、思考题

  1. 冒泡排序为什么叫"冒泡"排序?这个名字是怎么来的?

  2. 如果数组已经有序,优化后的冒泡排序需要比较多少次?

  3. 优化2中,为什么 newLastSwap = j 而不是 j+1

  4. 冒泡排序和直接插入排序相比,哪个在基本有序时效率更高?为什么?

欢迎在评论区讨论你的答案。

相关推荐
lxh01132 小时前
蜗牛排序题解
javascript·算法
胖咕噜的稞达鸭2 小时前
C/C++动态内存管理,malloc,calloc,realloc的区别,动态内存中的错误汇总
c语言·开发语言·c++
charlie1145141912 小时前
嵌入式C++教程实战之Linux下的单片机编程(6):从点亮第一盏LED开始 —— 我们为什么要用现代C++写STM32
linux·c语言·开发语言·c++·stm32·单片机
linux开发之路2 小时前
C++实现Whisper+Kimi端到端AI智能语音助手
c++·人工智能·llm·whisper·openai
艾莉丝努力练剑2 小时前
【Linux系统:多线程】线程概念与控制
linux·运维·服务器·c++·后端·学习·操作系统
AIminminHu2 小时前
OpenGL渲染与几何内核那点事-项目实践理论补充(二-1-(2):当你的CAD学会“听话”:从鼠标点击到自然语言命令)
c++·人工智能
极创信息2 小时前
不同开发语言程序如何做信创适配认证?完整流程与评价指标有哪些
java·c语言·开发语言·python·php·ruby·hibernate
airuike1232 小时前
高性能MEMS IMU:重构无人机飞行控制核心
人工智能·算法·重构·无人机
恒者走天下2 小时前
手机行业cpp c++相关就业岗位详细汇总
c++