1. qsort介绍
void qsort (void* base, size_t num, size_t size,
int (*compar)(const void*,const void*));
qsort一共四个参数:
- void* base: 要排序数组的起始地址,一般为数组名,也可以为任意有效地址 (比如arr, arr + 1, …)
- size_t num: 要排序的数据长度,一般为数组长度,也可以为任意可访问的无符号整形(比如len, len - 1, …)
- size_t size: 要排序的数组中单个元素所占字节数,一般为sizeof(*arr),其中*arr为数组首元素
- int (*compar)(const void*,const void*): 数组中元素的比较规则,函数指针类型,参数为两个void*类型的指针,返回值为int类型。用于传入一个自定义比较方式的函数地址
int compar(void* a, void* b)的返回值有特定的含义
先将void强转为指定自定义
// 以整型数据为例
int* pa = (int*)a;
int* pb = (int*)b;
- 当返回值大于0时,在a, b强转为自定义类型的指针后,返回前者元素自定义“大于”后者元素
- 当返回值小于0时,在a, b强转为自定义类型的指针后,返回前者元素自定义“小于”后者元素
- 当返回值等于0时,在a, b强转为自定义类型的指针后,返回前者元素自定义“等于”后者元素
2. 实现用于测试的比较规则函数
比较整形函数
int cmp_int(const void* a, const void* b) {
// 将a,强转为int*,使a解引用后可以访问四个字节
// 由于解引用后为整数可以直接比较
// 注意将a, b对调可使排序逆序
return *(int*)a - *(int*)b;
}
比较自定义类型
先定义一个自定义类型数组,以学生Stu为例:
typedef struct Stu {
char name[30];
int age;
}Stu;
再创建一个Stu类型的数组
Stu s[] = {
{"zhangsan", 19},
{"wangwu", 18},
{"lisi", 18},
{"zhaoliu", 20},
{"qianqi", 17}
};
实现打印Stu类型数组:
void print_Stu(Stu* s, int nums) {
printf("[\n");
for (int i = 0; i < nums; ++i) {
printf(i == nums - 1 ? " {%s, %d}\n" : " {%s, %d},\n", s[i].name, s[i].age);
}
printf("]\n");
}
main函数调用测试打印:
void test1() {
Stu s[] = {
{"zhangsan", 19},
{"wangwu", 18},
{"lisi", 18},
{"zhaoliu", 20},
{"qianqi", 17}
};
int nums = sizeof(s) / sizeof(*s);
print_Stu(s, nums);
}
实现Stu类型的比较规则
以
先比较年龄,由小到大,年龄相同按字典序(ASCII)比较姓名
为例
void cmp_Stu(const void* s1, const void* s2) {
// 将s1, s2强转为指向Stu类型的指针
Stu* p1 = (Stu*)s1;
Stu* p2 = (Stu*)s2;
// 年龄相同吗,相同按字典序(ASCII)比较姓名
// 不相同直接返回年龄差
// 注意将p1, p2对调可使排序逆序
return p1->age == p2->age ? strcmp(p1->name, p2->name) : p1->age - p2->age;
}
先使用库函数qsort()调用该排序规则
void test1() {
Stu s[] = {
{"zhangsan", 19},
{"wangwu", 18},
{"lisi", 18},
{"zhaoliu", 20},
{"qianqi", 17}
};
int nums = sizeof(s) / sizeof(*s);
printf("before sort: \n");
print_Stu(s, nums);
qsort(s, nums, sizeof(*s), cmp_Stu);
printf("after sort: \n");
print_Stu(s, nums);
}
3. 实现快速排序
快速排序讲解 @蓝不过嗨呀 @billbill ps: 不是我
参考挖坑法实现的快速排序算法(Java, int类型数组的排序)
// 快速排序函数组
public static void quickSort(int[] arr) {
// 封装一层减少传参
quickSort(arr, 0, arr.length - 1);
}
private static void quickSort(int[] arr, int left, int right) {
// 当递归到left不再小于right时停止递归
if (left >= right) {
return;
}
// 获得 基准元素 的索引
// 注意与partition()内的base区分
int base = partition(arr, left ,right);
quickSort(arr, left, base - 1);
quickSort(arr, base + 1, right);
}
private static int partition(int[] arr, int left, int right) {
// 以左值为 基准元素 保存一份(为元素值)
// 方便后续覆盖基准元素
int base = arr[left];
while (left < right) {
while (left < right && arr[right] >= base) {
right--;
}
arr[left] = arr[right];
while (left < right && arr[left] <= base) {
left++;
}
arr[right] = arr[left];
}
arr[left] = base;
return left;
}
改为c语言版的任意类型排序,即my_qsort()
因为多次涉及到单字节拷贝(c语言中char类型占一个字节,故传入char*类型指针),所以将其封装为一个函数copy_char
参数解释:
- char* dest: 目标起始地址
- const char* const src: 源起始地址,const防止修改源指针指向及该地址数据
- len: 拷贝的长度,在本文章可以看作,要排序数组内单个元素的所占字节数
该函数功能相当于同一类型的元素赋给左值
void copy_char(char* dest, const char* const src, size_t len) {
for (int i = 0; i < len; ++i) {
// 一个字节一个字节拷贝
*(dest + i) = *(src + i);
}
}
4. 外层的my_qsort()
暴露在外的接口与库函数qsort()一致
void my_qsort(void* arr, size_t len, size_t size, int (*compare_rule)(const void*, const void*)) {
// size即为单个要排序元素的大小
// 调用快排函数
quick_sort(arr, size, 0, len - 1, compare_rule);
}
快排函数的c语言实现
参数解释:
- 其中left,与right均为数组元素的索引
- compare_rule: 用于转入上文所实现的比较规则函数
void quick_sort(void* arr, size_t size, int left, int right, int (*compare_rule)(const void*, const void*)) {
if (left >= right) {
return;
}
// 调用partition()函数获得基准元素并将,基准元素放置到正确位置
// 返回基准索引
int base_index = partition(arr, size, left, right, compare_rule);
// 递归分别求基准元素左右的元素们,即将数组一分为二,并在每次递归时,
// 再次分别一分为二,进一步缩小未排序的范围
quick_sort(arr, size, left, base_index - 1, compare_rule);
quick_sort(arr, size, base_index + 1, right, compare_rule);
}
5. 内层的分区函数
- 返回值: 新的左值索引,作为新的基准值索引
int partition(void* arr, size_t size, int left, int right, int (*compare_rule)(const void*, const void*)) {
// 基准值的起始地址
char* base = (char*)arr + left * size;
// 使用动态内存分配保存一个要排序元素大小的内存
char* pivot = (char*)malloc(size);
// 储存基准元素
copy_char(pivot, base, size);
// 将void*指针强转为长度为一个字节的char*类型
char* a = (char*)arr + left * size;
char* b = (char*)arr + right * size;
while (left < right) {
// 当右边界值大于基准值时,右边界及a一直左移
while (left < right && compare_rule(b, pivot) >= 0) {
// right先自减,让b指向新的右边界地址
// 相当于(MyType*)b指向数组上一个元素
b = (char*)arr + --right * size;
}
// 当不满足上述条件时,将arr[right]赋值给arr[left](即坑位)
// 并在此地留下坑位
copy_char(a, b, size);
// 当左边界值小于基准值时,左边界及b一直右移
while (left < right && compare_rule(a, pivot) <= 0) {
// left先自增,让a指向新的左边界地址
// 相当于(MyType*)a指向数组下一个元素
a = (char*)arr + ++left * size;
}
// 当不满足上述条件时,将arr[left]赋值给arr[right](即坑位)
// 并在此地留下坑位
copy_char(b, a, size);
}
// 循环结束将基准值放到正确索引,即最后更新的坑位
copy_char(a, pivot, size);
// pivot使用完了以后及时释放内存
free(pivot);
return left;
}
至此my_qsort()实现完毕
6. 测试函数组
int类型测试
void print_arr_int(int* arr, int len) {
printf("[");
for (int i = 0; i < len; ++i) {
printf(i == len - 1 ? "%d" : "%d, ", arr[i]);
}
printf("]\n");
}
int cmp_int(const void* a, const void* b) {
return *(int*)a - *(int*)b;
}
void test0() {
int arr[] = {
1, 4, 3, 3, 2, 2, 3, 6, 8, 4
};
int len = sizeof(arr) / sizeof(*arr);
printf("before sort: \n");
print_Stu(s, nums);
my_qsort(s, nums, sizeof(*s), cmp_Stu);
printf("after sort: \n");
print_Stu(s, nums);
}
int main() {
test0();
return 0;
}
测试结果:
自定义类型Stu数组测试
// 比较规则
void cmp_Stu(const void* s1, const void* s2) {
Stu* p1 = (Stu*)s1;
Stu* p2 = (Stu*)s2;
return p1->age == p2->age ? strcmp(p1->name, p2->name) : p1->age - p2->age;
}
// 打印Stu数组内容
void print_Stu(Stu* s, int nums) {
printf("[\n");
for (int i = 0; i < nums; ++i) {
printf(i == nums - 1 ? " {%s, %d}\n" : " {%s, %d},\n", s[i].name, s[i].age);
}
printf("]\n");
}
// 测试函数
void test1() {
Stu s[] = {
{"zhangsan", 19},
{"wangwu", 18},
{"lisi", 18},
{"zhaoliu", 20},
{"qianqi", 17}
};
int nums = sizeof(s) / sizeof(*s);
printf("before sort: \n");
print_Stu(s, nums);
my_qsort(s, nums, sizeof(*s), cmp_Stu);
printf("after sort: \n");
print_Stu(s, nums);
}
int main() {
test1();
return 0;
}
测试输出:
到此qsort()以全部模拟实现完毕
源码
源码
END