【最新】冒泡排序C语言代码大全(3种解法,巨详细)
冒泡排序是初学者接触的第一个排序算法,也是 C 语言课程和数据结构课上最常讲的入门算法。它思路直观:让大的数像气泡一样一点一点"冒"到数组末尾,一趟冒出一个当前最大值,多趟下来整个数组就排好序了。
本文整理了冒泡排序的 3 种解法:标准版、优化版、优化进阶版,难度从浅到深。
和其他只贴代码的教程不同,本文的特点是图解 + 完整代码 + 逐行讲解三位一体:配图展示真实的排序过程,程序全部完整可运行,一次能排序任意 n 个数,新手照着敲一遍,就能看懂冒泡排序的原理,也知道怎么把它写得更高效。
以无序表 {49, 38, 65, 97, 76, 13, 27, 49} 为例做升序排序。第一趟冒泡的过程是:
先比较 49 和 38,49 比 38 大,交换,无序表变成 {38, 49, 65, 97, 76, 13, 27, 49};
经过这一趟,最大值 97 就被"冒"到了表的最后一个位置。
第一趟结束后,97 已经确定是最大值,排在最后,接下来的排序就和它无关了。第二趟只需要对剩下的 7 个数重复同样的两两比较,把除 97 之外的最大值 76 找出来,放到倒数第二个位置:
就这样一趟一趟地比较,每趟都从剩余记录中"冒"出一个当前最大值放到末尾,直到某一趟发现没有任何交换发生(说明已经全部有序),或者比较趟数达到了 n-1(n 为数字个数),排序就结束。
推广到一般情况:n 个数最多 n-1 趟一定能排完,每一趟确定一个数的最终位置,n 个数里 n-1 个位置确定了,剩下的最后一个自然就位。
这里要讲三个关键点:
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 结束:
这个优化对"基本有序"的数据效果立竿见影:极端情况下输入的 n 个数本来就是升序的,第一趟做完发现没有交换,一趟就结束,比较次数从 n(n-1)/2 直接降到 n-1。外层循环即使写成 n 趟也不用担心,因为有序后第一趟就会被 break 拦下。
这种解法是冒泡排序最常见、性价比最高的一种优化,实际写代码时推荐默认使用。
再次优化的思考点:每一趟中,最后一次发生交换的位置之后的所有元素,其实都已经有序了,下一趟完全不用再比到老地方,直接把比较终点缩到"最后一次交换的位置"就行。因为每趟的终点是动态变化的,这里外层循环改用 while 表达更自然:
对一般情况就是:每趟结束后把 end 更新为最后一次交换的下标,下一趟只比较 0 到 end 之间的相邻对。这相当于把解法二的"末尾 i 个已排好"升级成"从最后一次交换位置往后都已排好",减少的无用比较更多,是冒泡排序所有常见写法里效率最高的一种。
对示例这种 8 个数的小规模数据来说,三种写法的差别几乎感觉不到;但把 n 换成几万、几十万,或者输入一段"前半乱序、后半有序"的数据,解法二和解法三节省的时间就会非常明显。冒泡排序本身就慢(时间复杂度 O(n²)),所以能省一趟是一趟,这也是各种优化存在的意义。
本文的程序都做成了通用的 n 个数版本,先输入 n 再输入数据,代码里需要改的只是数组容量,想排几个数都行。
冒泡排序的三种解法从浅到深,正好展示了同一个算法一步步优化的完整思路:
把这三个基础排序的原理和代码都吃透,再去看快速排序、归并排序等进阶算法会轻松很多。
声明:当前文章为本站“玩转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语言和数据结构”官方原创,由国家机构和地方版权局所签发的权威证书所保护。


ICP备案:
公安部网络备案: