作者:解学武

【最新】冒泡排序C语言代码大全(3种解法,巨详细)

冒泡排序是初学者接触的第一个排序算法,也是 C 语言课程和数据结构课上最常讲的入门算法。它思路直观:让大的数像气泡一样一点一点"冒"到数组末尾,一趟冒出一个当前最大值,多趟下来整个数组就排好序了。

本文整理了冒泡排序的  3 种解法:标准版、优化版、优化进阶版,难度从浅到深。

和其他只贴代码的教程不同,本文的特点是图解 + 完整代码 + 逐行讲解三位一体:配图展示真实的排序过程,程序全部完整可运行,一次能排序任意 n 个数,新手照着敲一遍,就能看懂冒泡排序的原理,也知道怎么把它写得更高效。

文章内容取自:C语言入门教程:零基础学会C语言(2026版)

冒泡排序的原理

冒泡排序(也叫起泡排序)的核心思想:从前往后两两比较相邻的记录,如果前一个比后一个大,就交换它们的位置。一趟比较下来,最大的数会被一步一步"推"到最后面,就像气泡从水底浮到水面一样,所以叫冒泡排序。

以无序表 {49, 38, 65, 97, 76, 13, 27, 49} 为例做升序排序。第一趟冒泡的过程是:


先比较 49 和 38,49 比 38 大,交换,无序表变成 {38, 49, 65, 97, 76, 13, 27, 49};
  • 接着比较 49 和 65,49 比 65 小,不交换;
  • 再比较 65 和 97,不交换;
  • 比较 97 和 76,97 大,交换;
  • 比较 97 和 13,交换;
  • 比较 97 和 27,交换;
  • 最后比较 97 和 49,97 大,交换。

经过这一趟,最大值 97 就被"冒"到了表的最后一个位置。

第一趟结束后,97 已经确定是最大值,排在最后,接下来的排序就和它无关了。第二趟只需要对剩下的 7 个数重复同样的两两比较,把除 97 之外的最大值 76 找出来,放到倒数第二个位置:


就这样一趟一趟地比较,每趟都从剩余记录中"冒"出一个当前最大值放到末尾,直到某一趟发现没有任何交换发生(说明已经全部有序),或者比较趟数达到了 n-1(n 为数字个数),排序就结束。

推广到一般情况:n 个数最多 n-1 趟一定能排完,每一趟确定一个数的最终位置,n 个数里 n-1 个位置确定了,剩下的最后一个自然就位。

标准冒泡排序(解法一)

标准版是最直观的写法:n 个数就老老实实跑 n-1 趟,每趟把"未排好部分"里的最大值冒到最后。它不做任何优化,最适合用来理解冒泡排序本身。程序先用 scanf 读入 n 和 n 个数,排序后输出结果:
#include <stdio.h>

// 交换两个变量的值,用指针才能改到实参
void swap(int *a, int *b){
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

int main(){
    int array[100];              // 数组容量:最多排 100 个数,不够可改大
    int n, i, j;

    printf("请输入要排序的数字个数 n:");
    scanf("%d", &n);
    printf("请输入 %d 个数:", n);
    for(i = 0; i < n; i++){       // 依次读入 n 个数
        scanf("%d", &array[i]);
    }

    // 冒泡排序:n 个数最多需要 n-1 趟
    for(i = 0; i < n - 1; i++){
        // 内层循环:末尾 i 个位置已确定,比较范围每趟缩小 1
        for(j = 0; j < n - 1 - i; j++){
            if(array[j] > array[j+1]){   // 前一个比后一个大就交换
                swap(&array[j], &array[j+1]);
            }
        }
    }

    // 输出排序结果
    printf("排序结果:");
    for(i = 0; i < n; i++){
        printf("%d ", array[i]);
    }
    printf("\n");
    return 0;
}
运行结果(输入示例用本文开头的 8 个数):
请输入要排序的数字个数 n:8
请输入 8 个数:49 38 65 97 76 13 27 49
排序结果:13 27 38 49 49 65 76 97
把 n 换成其他数字、输入任意多个数都能排,比如输入 5 个数 5 3 8 1 2,输出就是 1 2 3 5 8。

这里要讲三个关键点:
1) 交换为什么要用指针。 交换两个数不能直接写 array[j] = array[j+1],那样会把前一个数覆盖掉。正确做法是借助中间变量,或者像代码里这样封装一个 swap 函数。C 语言函数传参是传值的,要真正修改 main 里的数组元素,swap 的参数必须写成指针(int *a),调用时传地址(&array[j])。

2) 内层循环的边界是核心。 for(j = 0; j < n - 1 - i; j++) 比较的是下标 (0,1)、(1,2)……(n-2-i, n-1-i) 这些相邻对。第 i 趟结束后,数组末尾的 i 个数已经是排好的最大值,不需要再参与比较,所以上界要减 i。如果写成 j < n,当 j=n-1 时会去访问不存在的 array[n],造成数组越界,这是最危险的错误。

3) 为什么是 n-1 趟而不是 n 趟。 每一趟至少能确定一个数的最终位置,n 个数里有 n-1 个位置确定了,剩下的最后一个自然就位,所以最多 n-1 趟就一定能排完。标准版不管数据是否已经有序,都会把 n-1 趟全部跑完,这也是它唯一"笨"的地方,解法二就是冲着这个浪费去的。

优化冒泡排序(解法二)

解法一最浪费的地方在于,就算数组中途已经有序,它也会把剩下的趟数全部跑完。

这个解法加一个标志变量 key,记录"本趟有没有发生过交换",如果一整趟下来一次交换都没有,说明所有相邻元素已经前小后大,整个数组有序,可以立刻 break 结束:
#include <stdio.h>

void swap(int *a, int *b){
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

int main(){
    int array[100];              // 数组容量:最多排 100 个数,不够可改大
    int n, i, j, key;

    printf("请输入要排序的数字个数 n:");
    scanf("%d", &n);
    printf("请输入 %d 个数:", n);
    for(i = 0; i < n; i++){
        scanf("%d", &array[i]);
    }

    for(i = 0; i < n; i++){      // 外层最多 n 趟,有序后会被 break 掉
        key = 0;                 // 每趟开始前,假设本趟没有交换
        for(j = 0; j + 1 < n - i; j++){   // 相邻两两比较
            if(array[j] > array[j+1]){
                swap(&array[j], &array[j+1]);
                key = 1;         // 发生过交换,标记为 1
            }
        }
        if(key == 0){            // 整趟零交换,说明已经有序
            break;               // 提前结束
        }
    }

    printf("排序结果:");
    for(i = 0; i < n; i++){
        printf("%d ", array[i]);
    }
    printf("\n");
    return 0;
}
运行结果(输入与解法一完全相同,输出也相同):
请输入要排序的数字个数 n:8
请输入 8 个数:49 38 65 97 76 13 27 49
排序结果:13 27 38 49 49 65 76 97
和解法一对比,变化只有两处:每趟开始前把 key 清零,内层循环结束后判断 key == 0 就 break。

这个优化对"基本有序"的数据效果立竿见影:极端情况下输入的 n 个数本来就是升序的,第一趟做完发现没有交换,一趟就结束,比较次数从 n(n-1)/2 直接降到 n-1。外层循环即使写成 n 趟也不用担心,因为有序后第一趟就会被 break 拦下。

这种解法是冒泡排序最常见、性价比最高的一种优化,实际写代码时推荐默认使用。

再次优化冒泡排序(解法三)

解法二能拦截"整个数组已经有序"的情况,但遇到"后半段有序、前半段乱序"的数据时,每一趟还是会把后半段白白比较一遍。

再次优化的思考点:每一趟中,最后一次发生交换的位置之后的所有元素,其实都已经有序了,下一趟完全不用再比到老地方,直接把比较终点缩到"最后一次交换的位置"就行。因为每趟的终点是动态变化的,这里外层循环改用 while 表达更自然:
#include <stdio.h>

void swap(int *a, int *b){
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

int main(){
    int array[100];              // 数组容量:最多排 100 个数,不够可改大
    int n, j, end, lastSwap;

    printf("请输入要排序的数字个数 n:");
    scanf("%d", &n);
    printf("请输入 %d 个数:", n);
    for(j = 0; j < n; j++){
        scanf("%d", &array[j]);
    }

    end = n - 1;                 // 初始:需要比较到下标 n-1
    while(end > 0){
        lastSwap = 0;            // 每趟开始前,假设本趟没有交换
        for(j = 0; j < end; j++){
            if(array[j] > array[j+1]){
                swap(&array[j], &array[j+1]);
                lastSwap = j;    // 更新最后一次交换的位置
            }
        }
        if(lastSwap == 0){       // 一趟零交换,已经有序
            break;
        }
        end = lastSwap;          // 下一趟只需要比较到 lastSwap 为止
    }

    printf("排序结果:");
    for(j = 0; j < n; j++){
        printf("%d ", array[j]);
    }
    printf("\n");
    return 0;
}
运行结果(输入与解法一完全相同,输出也相同):
请输入要排序的数字个数 n:8
请输入 8 个数:49 38 65 97 76 13 27 49
排序结果:13 27 38 49 49 65 76 97
以示例的 8 个数为例:
  • 第一趟把 97 冒到最后的过程中,最后一次交换发生在下标 6 的位置(97 和最后一个 49 交换),说明下标 7 已经放好,下标 6 之后的区域都不需要再比,于是把 end 从 7 缩到 6;
  • 第二趟的 76 最后落在下标 5,end 又缩到 5……比较范围一趟比一趟小,很多趟甚至只比较前面一小段。

对一般情况就是:每趟结束后把 end 更新为最后一次交换的下标,下一趟只比较 0 到 end 之间的相邻对。这相当于把解法二的"末尾 i 个已排好"升级成"从最后一次交换位置往后都已排好",减少的无用比较更多,是冒泡排序所有常见写法里效率最高的一种。

三种解法怎么选

三种解法的排序结果完全一样,区别只在"做了多少无用功":
  • 解法一结构最简单、最好背,教学和作业里最常用;
  • 解法二代码只比解法一多三行,性价比最高,实际写代码时推荐默认用它;
  • 解法三进一步缩小每趟的比较范围,数据量较大或部分有序时效率最好,但代码稍微绕一点,适合学有余力时掌握。

对示例这种 8 个数的小规模数据来说,三种写法的差别几乎感觉不到;但把 n 换成几万、几十万,或者输入一段"前半乱序、后半有序"的数据,解法二和解法三节省的时间就会非常明显。冒泡排序本身就慢(时间复杂度 O(n²)),所以能省一趟是一趟,这也是各种优化存在的意义。

总结

冒泡排序的价值不在效率,而在于它是理解"排序到底在干什么"的最好入门:一趟确定一个最大值的位置,多趟完成全部排序。

本文的程序都做成了通用的 n 个数版本,先输入 n 再输入数据,代码里需要改的只是数组容量,想排几个数都行。

冒泡排序的三种解法从浅到深,正好展示了同一个算法一步步优化的完整思路:
  • 标准版负责讲清原理;
  • 解法二用标志位拦截"整体有序"的情况;
  • 解法三再记录最后一次交换的位置,把"后半段有序"时的无效比较也一并省掉。

把这三个基础排序的原理和代码都吃透,再去看快速排序、归并排序等进阶算法会轻松很多。

声明:当前文章为本站“玩转C语言和数据结构”官方原创,由国家机构和地方版权局所签发的权威证书所保护。

添加微信咨询 加站长微信免费领
C语言学习小册
加站长微信免费领C语言学习小册
微信ID:xiexuewu333