加油,start anew!
mj starbucks
好好享受现在的时光,不停的朝着目标前进,keep moving,行动不停!
1. 冒泡排序(基础版 + 优化版)💧
1.1 个人理解
冒泡排序是通过相邻元素两两比较交换,把大元素逐步 "冒" 到数组末尾。
1.2 深入
基础冒泡排序逻辑
👉 核心动作:从数组第一个元素开始,依次比较相邻的两个元素(arr[j]和arr[j+1]),如果前一个比后一个大就交换位置;每一轮外层循环都会把当前未排序部分的最大元素放到末尾。
⚠️ 我写的初级冒泡函数有个错误:比较的是arr[i]和arr[i+1],应该是arr[j]和arr[j+1](复习时要注意)。
优化版冒泡排序(提前终止)
👉 优化原因:基础版不管数组是否已排好序,都会走完所有循环,效率低;
👉 优化思路:加计数器count,每轮内层循环开始时置 0,发生交换则count++;若一轮循环后count=0,说明数组已完全有序,直接break跳出循环,减少无效比较。
数组传参本质(地址传递)
👉 我最初疑惑 "自定义函数改不了主函数内容",但数组传参的本质是传递首元素地址,非常方便,因此在冒泡函数里修改数组元素,主函数的原数组会同步变化 ------ 这是 C 语言数组传参的关键(没有传整个数组,只传地址)📌。
1.3 专业知识
冒泡排序属于交换排序,时间复杂度:最好情况(已排序)O(n),最坏情况(逆序)O(n²);空间复杂度O(1)(原地排序)。其核心特征是 "相邻比较、逆序交换",每轮确定一个最大元素的位置;优化版通过提前终止条件,降低了已排序数组的排序耗时,是基础版的效率优化手段。
1.4 代码实现
cpp
void myprint(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
//初级冒泡函数
void bubblesort(int arr[], int sz)
{
int i = 0;
int j = 0;
for (i = 0; i < sz; i++)
{
for (j = 0; j < sz - i - 1; j++)
{
if (arr[i] > arr[i + 1])
{
int t = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = t;
}
}
}
}
//改进的冒泡函数:
//就是,如果对于第i次外层循环,里面的for循环没有一次让两个相邻的数交换,那么就可以停下了
void bubblesort(int arr[], int sz)
{
int i = 0;
int j = 0;
for (i = 0; i < sz; i++)
{
int count = 0;
for (j = 0; j < sz - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
int t = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = t;
count++;
}
}
if (count == 0)
break;
}
}
int main()
{
int arr[10] = { 1,3,4,2,5,6,9,8,7,10 };
myprint(arr, 10);
bubblesort(arr, 10);
myprint(arr, 10);
return 0;
}
2. qsort 函数(C 内置通用排序)🔧
2.1 个人理解
qsort 是 C 语言内置的通用排序函数,默认按升序排列,比较函数自己写。
2.2 深入
qsort 四大参数解析
👉 qsort 的 4 个参数缺一不可,每个参数都有明确作用:
- 第一个参数:待排序数组的首元素地址(
void*类型,支持任意类型数组); - 第二个参数:数组元素个数(
size_t类型,无符号整数,比int更适合表示 "个数"); - 第三个参数:单个元素的字节长度(
size_t类型,比如int占 4 字节、结构体按实际大小算); - 第四个参数:函数指针(比较函数),需自己编写,用来定义两个元素的比较规则。
比较函数编写规则
👉 固定格式:返回值为int,两个参数都是const void*(const保证不修改元素,void*适配任意类型);
👉 编写步骤:先把const void*强制转换为对应数据类型的指针,再解引用获取值比较;
👉 示例(整型):return *(int*)a - *(int*)b; ------ 强制转int*后解引用,相减结果决定排序顺序。
qsort 排序规则(有点像减法,然后把大的放后面)
👉 返回正数:第一个元素 > 第二个元素,qsort 会把第一个元素排在后面(升序逻辑);
👉 返回 0:两个元素相等,位置不变;
👉 返回负数:第一个元素 < 第二个元素,排在前面;
👉 总结:升序返回a-b,降序返回b-a⚠️。
2.3 专业
qsort 底层实现为快速排序(Quick Sort),平均时间复杂度O(n log n),最坏O(n²)。其泛型特性通过void*指针(无类型指针)和自定义比较函数实现,支持整型、浮点型、结构体等任意类型数据排序,是 C 语言中高效、通用的排序工具。size_t是 C 标准定义的无符号整数类型,专门用于表示 "对象大小 / 元素个数",比int更符合语义。
2.4 适用场景
- 需对任意类型数组排序,且不想手动实现复杂排序算法时;
- 对排序效率有一定要求(快排比基础冒泡排序高效得多);
- 项目中需要统一、规范的排序接口时。
2.5 代码实现(整型排序)
cpp
#include<stdlib.h>
void myprint(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int cmp_int(const void* a, const void* b)
{
return *(int*)a - *(int*)b;//强制类型转换之后再解引用
}
void test2()
{
int arr[10] = { 2,3,5,6,8,9,1,4,7,10 };
myprint(arr, 10);
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), cmp_int);
myprint(arr, 10);
}
3. 结构体排序(qsort 实战)👥
3.1 前置:结构体指针箭头操作符
个人理解
结构体指针的箭头操作符->可直接通过结构体指针访问成员 ,比(*ps).name更简洁。
深入
👉 结构体成员的两种访问方式:
- 结构体变量:用点操作符
.,比如s.name、s.age; - 结构体指针:
- 方式 1(解引用 + 点):
(*ps).name; - 方式 2(箭头):
ps->name(无需写解引用,代码更简洁);
- 方式 1(解引用 + 点):
👉 箭头操作符的优势:函数传结构体指针时,用->访问成员更直观,减少代码冗余。
专业
->是 C 语言中结构体 / 联合体指针的成员访问操作符,运算优先级高于解引用*,仅适用于指针类型。ps->name是(*ps).name的语法糖(语法简化形式),目的是提升代码可读性,是结构体指针成员访问的最佳实践。
代码实现
cpp
void test(struct Stu* ps)
{
//我们熟知的打印方法(通过解引用):
printf("%s\n", (*ps).name);
printf("%d\n", (*ps).age);
//(结构体地址)结构体成员访问操作符(箭头操作符)的使用:
printf("%s\n", ps->name);
printf("%d\n", ps->age);
}
3.2 按结构体年龄排序
个人理解
按结构体年龄排序的比较函数,需把void*转成结构体指针,用->访问age成员,返回两数之差实现升序。
深入
👉 强制类型转换的原因:qsort 传的比较函数参数是void*,必须转成struct Stu*才能访问****age成员;(强制类型转换)
👉 两种写法对比:
- 普通版:
(*(struct Stu*)p1).age - (*(struct Stu*)p2).age(先解引用,再用.); - 箭头版:
((struct Stu*)p1)->age - ((struct Stu*)p2)->age(直接用->,更简洁);
👉 排序逻辑:返回值为正 → p1年龄更大 → 排在p2后面,实现年龄升序。
专业
结构体成员比较的核心是 "类型映射":将void*指针强制转换为具体的结构体类型指针,再访问目标成员。比较函数返回值需符合 qsort 约定,通过成员值的差值控制升序 / 降序;->的使用符合 C 语言代码规范,是结构体指针访问成员的推荐写法。
代码实现
cpp
//按照年龄
int cmp_stu_by_age(const void* p1,const void*p2)//对结构体中年龄的排序
{
//普通版本:
return (*(struct Stu*)p1).age - (*(struct Stu*)p2).age;
//使用结构体访问操作符(结构体指针,箭头)
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
3.3 按结构体姓名排序(字符串)
个人理解
结构体姓名排序要用strcmp函数比较字符串,返回其结果,因为字符串不能直接用-比较,strcmp返回值还完美适配 qsort 规则,挺好。
深入
👉 为什么不能用-比较字符串?字符串是字符数组,直接减是比较地址(不是内容),必须用strcmp;
👉 strcmp的作用:按 ASCII 码值逐字节比较两个字符串(字典序 / 字母顺序);
👉 strcmp返回值(++完美适配 qsort++):
- 正数:第一个字符串 > 第二个;
- 0:两个字符串相等;
- 负数:第一个字符串 < 第二个;
⚠️注意:使用strcmp必须包含string.h头文件!
专业
字符串比较必须使用库函数strcmp(<string.h>),其实现逻辑是逐字节比较字符的 ASCII 码值,直到遇到'\0'或不同字符。strcmp的返回值语义与 qsort 比较函数完全兼容,是字符串类型排序的标准实现方式。
代码实现
cpp
#include<string.h>
//按照姓名
//字符串比较的时候用strcmp(字符串1,字符串2) ,需要头文件string.h,比较的是字母顺序(ASCII码值)
//这个函数的返回的和qsort是相应的
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name,((struct Stu*)p2)->name);
}
3.4 结构体排序完整代码(test3)
cpp
//对结构体
struct Stu
{
char name[30];
int age;
};
void print_stu(struct Stu arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s : %d\n", arr[i].name, arr[i].age);
}
printf("\n");
}
void test3()//结构体该复习复习了
{
struct Stu arr[] = { {"zhangsan",20},{"lisi",38},{"wangwu",18} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
//对结构体的打印:
print_stu(arr, sz);
}
4. 通用冒泡排序(模仿 qsort)🚀
4.1 个人理解
模仿 qsort ,拆分的思想。
4.2 深入
核心:泛型化设计思路
👉 基础冒泡的局限性:只针对int类型,比较和交换都是硬编码;
👉 通用冒泡的关键设计:
- 参数模仿 qsort:
void* base(首地址)、size_t sz(元素数)、size_t width(元素字节数)、比较函数指针; - 地址定位:把
void*转成char*(1 字节),通过j*width找到第 j 个元素地址,(j+1)*width找到下一个; - 比较逻辑:调用++自定义比较函数++,判断是否需要交换;
- 交换逻辑:按字节交换(不管元素类型),实现通用交换。
Swap 函数(字节级交换)
👉 字节级交换的原因:不知道元素类型(int / 结构体等),但所有类型都可拆分为字节;
👉 实现逻辑:循环width次(元素字节数),每次交换char*指针指向的 1 个字节,指针 ++,直到所有字节交换完成🔄;
👉 示例:int占 4 字节 → 循环 4 次,逐字节交换后,整个int值完成交换。
bubblesort2 函数逻辑
👉 外层循环:i从 0 到sz-1(我最初写成sz是错误的❌,sz 个元素只需 sz-1 轮);
👉 内层循环:j从 0 到sz-i-1(和基础冒泡一致);
👉 比较:调用cmp函数,传入第 j 和 j+1 个元素地址,返回值 > 0 则交换;
👉 交换:调用Swap函数,传入元素地址和字节数;
👉 提前终止:加count计数器,无交换则break(和优化版冒泡一致)。
4.3 专业
通用冒泡排序的核心是泛型编程思想(C 语言通过void*和字节操作实现),char*指针是实现任意类型访问的关键(char 为最小内存单元,占 1 字节)。该实现的时间复杂度与基础冒泡一致(最好O(n),最坏O(n²)),但具备 qsort 的泛型特性,可适配任意数据类型。Swap函数的字节级交换是通用数据交换的标准方式,避免为不同类型编写多个交换函数。
4.4 适用场景
- 学习泛型编程思想,理解 qsort 的底层泛型实现逻辑;
- 数据量小、场景简单,且需自定义排序规则的任意类型数组排序;
- 不希望依赖标准库 qsort,需手动实现通用排序时。
4.5 代码实现
Swap 函数
cpp
void Swap(char* buf1, char* buf2, size_t width)
{
int i = 0;
char tmp = 0;
for (i = 0; i < width; i++)
{
tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
bubblesort2 函数
cpp
//和qsort函数模仿(用冒泡函数的基本)
void bubblesort2(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{
//底层还是冒泡排序的算法思想:
int i = 0;
int j = 0;
for (i = 0; i < sz-1; i++)//我最开始写成了sz,错误❌
{
int count = 0;
for (j = 0; j < sz - i - 1; j++)
{
if (cmp(((char*)base + j * width), (char*)base + (j + 1) * width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
count++;
}
}
if (count == 0)
break;
}
}
整型测试(test4)
cpp
void test4()
{
int arr[10] = { 1,3,4,2,5,6,9,8,7,10 };
myprint(arr, 10);
bubblesort2(arr, 10,sizeof(arr[0]),cmp_int);
myprint(arr, 10);
}
结构体测试(test5)
cpp
void test5()
{
struct Stu arr[] = { {"zhangsan",20},{"lisi",38},{"wangwu",18} };
int sz = sizeof(arr) / sizeof(arr[0]);
bubblesort2(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
bubblesort2(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
//对结构体的打印:
print_stu(arr, sz);
}
5. 核心要点总结 📝
- 冒泡排序:基础版靠相邻交换排序,优化版加
count提前终止;数组传参本质是传地址,函数内修改会影响原数组。 - qsort 函数:通用排序工具,依赖自定义比较函数;
void*适配任意类型,strcmp适配字符串比较,->简化结构体指针成员访问。 - 通用冒泡:模仿 qsort 的泛型设计,通过
char*实现字节级地址定位,Swap函数逐字节交换实现任意类型数据交换。
6. 完整主函数代码
int main()
{
test2();//对qsort函数的使用(int)
test3();
struct Stu s = { "zhangsan",20 };
test(&s);//对(结构体地址)结构体成员访问操作符(箭头操作符)用法的说明:
test4();
test5();
return 0;
}
