1 基础版
cpp
//冒泡排序
#include <stdio.h>
bubble_sort(int arr[], int len)
{
int i = 0;
int j = 0;
for (i = 0; i < len - 1; i++)
{
int flag = 1;
for (j = 0; j < len - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
arr[j] = arr[j] ^ arr[j + 1];
arr[j + 1] = arr[j] ^ arr[j + 1];
arr[j] = arr[j] ^ arr[j + 1];
//等价于:
/*int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;*/
flag = 0;
}
}
if (flag == 1)
break;
}
}
int main()
{
int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
冒泡排序的核心思路
冒泡排序的本质是通过相邻元素的比较和交换,让大的元素像 "气泡" 一样逐步 "浮" 到数组的末尾,每一轮循环都会确定一个最大元素的最终位置,直到整个数组有序。
- 外层循环:控制排序的轮数,一个长度为
len的数组,最多需要len-1轮排序(因为最后一个元素会自动归位)。

代码中的数组长度是 10,最多需要 9 轮排序,每轮确定一个最大数的位置(第 1 轮把最大数放到最后,第 2 轮把次大数放到倒数第 2 位,以此类推)
第一轮循环:

- 内层循环:每一轮的相邻元素比较交换

思路:每完成一轮排序,末尾的 i 个元素已经是有序的(最大的 i 个数),所以内层循环只需比较前 len-i-1 个元素,避免重复比较
举例:第 1 轮(i=0),内层循环比较 0~8 索引(共 9 次),把最大数放到索引 9;第 2 轮(i=1),内层循环比较 0~7 索引(共 8 次),把次大数放到索引 8,以此类推。

- (优化)提前终止已排好序的循环(flag 标志位)

- 初始化
flag=1:假设本轮循环中没有任何元素交换(即数组已经有序)。- 如果发生交换,说明数组还没排好,把
flag=0。 - 本轮循环结束后,如果
flag还是 1,说明没有任何交换发生,数组已经完全有序,直接跳出外层循环,不用继续后面的轮次。
- 如果发生交换,说明数组还没排好,把
- 举例:如果你的数组本来就是
{0,1,2,3,4},第一轮循环就不会有任何交换,flag=1,直接终止排序,不用执行剩下的 4 轮循环,提升效率。
2.通用版冒泡排序(maopao2)
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
void Swap(char*buf1,char*buf2,size_t width)
{
for (int i = 0; i < width; i++)
{
char temp = *buf1;
*buf1 = *buf2;
*buf2 = temp;
buf1++;
buf2++;
}
}
void maopao2(void * base, size_t sz,size_t width,int (*cmp)(const void*p1,const void*p2 ))
{
for (int i = 0; i < sz - 1; i++)
{
for (int j = 0; j < sz - i - 1; j++)
{
//排序函数的输入是void* base(无类型指针),它本身不支持算术运算(不能直接base+1),
// 也不知道数组元素的具体类型,所以必须通过char*来实现 "精准字节级定位"。
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//交换两个元素
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
struct student
{
int age;
char name[20];
};
int cmp(const void* a, const void* b)
{
return *(int*)b - *(int*)a;
}
int cmp2(const void* a, const void* b)
{
return strcmp(((struct student*)a)->name, ((struct student*)b)->name);
}
int main()
{
int a[10] = { 10,90,30,44,450,60,70,80,20,109 };
int sz = sizeof(a) / sizeof(a[0]);
struct student st[] = {{20,"xiaoming"},{25,"aiaohong"},{30,"ziaoli"}};
int st_sz = sizeof(st) / sizeof(st[0]);
maopao2(st, st_sz, sizeof(st[0]), cmp2);
for(int i=0;i<3;i++)
{
printf("%s ",st[i].name);
}
printf("\n");
maopao2(a, sz, sizeof(a[0]), cmp);
for (int i = 0; i < sz; i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
优化:
cpp
#define _CRT_SECURE_NO_WARNINGS // 消除VS环境下的安全函数警告
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 功能:字节级交换两个元素(支持任意类型)
// 参数:buf1-第一个元素地址,buf2-第二个元素地址,width-单个元素的字节大小
void Swap(char* buf1, char* buf2, size_t width)
{
// 按字节交换:循环width次,确保不同类型(int/struct等)都能完整交换
for (int i = 0; i < width; i++)
{
char temp = *buf1;
*buf1 = *buf2;
*buf2 = temp;
buf1++; // 指针移动1字节(char*是最小字节单位,保证精准定位)
buf2++;
}
}
// 通用版冒泡排序:支持任意类型数组排序
// 参数:
// base-数组首地址(void*无类型指针,可接收任意类型)
// sz-数组元素个数
// width-单个元素的字节大小(通过sizeof计算)
// cmp-比较函数指针(自定义排序规则:返回>0则交换,=0相等,<0不交换)
void maopao2(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{
// 外层循环:控制排序趟数
for (int i = 0; i < sz - 1; i++)
{
int flag = 1; // 优化标记:标记本趟是否发生交换
// 内层循环:控制每趟比较次数,逐趟减少i次
for (int j = 0; j < sz - i - 1; j++)
{
// 关键:void*指针不能直接算术运算(无类型则无字节大小概念)
// 转换为char*:按1字节偏移,通过 j*width 定位第j个元素,(j+1)*width定位第j+1个元素
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
// 调用字节交换函数,交换两个元素
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
flag = 0; // 发生交换,重置标记
}
}
if (flag == 1) // 本趟无交换,数组已有序,提前退出
break;
}
}
// 定义学生结构体(用于测试自定义类型排序)
struct student
{
int age;
char name[20];
};
// 比较函数1:int类型降序排序
// 返回值>0:p1对应的值 > p2对应的值,需要交换(实现降序)
int cmp_int_desc(const void* a, const void* b)
{
// 先将void*转换为int*,再解引用获取值(必须强转,否则无法解引用)
return *(int*)b - *(int*)a;
}
// 比较函数2:学生结构体按name升序排序(字典序)
int cmp_student_name(const void* a, const void* b)
{
// 转换为struct student*,通过->访问name成员,调用strcmp比较字符串
return strcmp(((struct student*)a)->name, ((struct student*)b)->name);
}
int main()
{
// 测试1:int数组降序排序
int a[10] = { 10,90,30,44,450,60,70,80,20,109 };
int sz_int = sizeof(a) / sizeof(a[0]);
maopao2(a, sz_int, sizeof(a[0]), cmp_int_desc);
printf("通用版排序结果(int降序):");
for (int i = 0; i < sz_int; i++)
{
printf("%d ", a[i]);
}
printf("\n");
// 测试2:学生结构体按name升序排序
struct student st[] = { {20,"xiaoming"}, {25,"aiaohong"}, {30,"ziaoli"} };
int sz_st = sizeof(st) / sizeof(st[0]);
maopao2(st, sz_st, sizeof(st[0]), cmp_student_name);
printf("通用版排序结果(学生name升序):");
for (int i = 0; i < sz_st; i++)
{
printf("%s ", st[i].name);
}
printf("\n");
return 0;
}
-
void*无类型指针:作为函数参数可接收任意类型的数组首地址,但不能直接算术运算(无字节大小信息),必须转换为char*(1字节单位)才能精准定位元素。
-
字节级交换(Swap函数):通过循环交换每个字节,确保无论元素类型是4字节的int还是24字节的student结构体,都能完整交换。
-
函数指针(cmp):将排序规则与排序算法解耦,用户只需实现不同的比较函数,就能实现升序、降序或自定义类型排序,大幅提升灵活性。