题目介绍
使用冒泡排序自主实现C语言的库函数——qsort
介绍qsort函数
qsort函数是C语言的库函数,它是以快排为底层逻辑实现的排序函数,可以给任意数据类型排序。
qsort官网介绍
也可以查看我之前写的如何使用qsort函数的博客:链接
qsort函数的自主实现(冒泡排序为底层逻辑)
接下来就让我们一起使用冒泡排序来实现qsort函数吧
首先我们试着使用冒泡排序给一个数组进行升序排序,代码如下:
#include <stdio.h>
void bubble_sort(int arr[], int sz)
{
//有序数的边界(我们只需判断其左侧的无序数)
int limit = sz - 1;
//标记每一轮最后一次交换的位置
int last_change = 0;
//进行sz-1轮排序
for (int i = 0; i < sz - 1; i++)
{
//假设没有进行替换(数组已经有序)
int flag = 0;
//每一轮比较limit次,即只比较无序的数
for (int j = 0; j < limit; j++)
{
if (arr[j] > arr[j + 1])
{
//交换
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
//如果交换了,则标记数组无序
flag = 1;
//记录最后一次交换的位置
last_change = j;
}
}
//每一轮结束后,更新有序数的边界,其边界就是上一轮最后一次交换的位置
limit = last_change;
//如果没有交换,则数组已经有序,直接跳出循环
if (0 == flag)
{
break;
}
}
}
int main()
{
int arr[10] = {
71,114,105,62,44,47,93,80,56,121 };
int sz = sizeof(arr) / sizeof(arr[0]);
printf("排序前: ");
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
bubble_sort(arr, sz);
printf("\n排序后(升序): ");
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
程序运行结果:
具体实现方式可以参考我之前的博客:链接
接下来就真正开始自主实现qsort函数了
将形参重新修改,并且删除判断数据大小和交换数据的部分后,我们能够得到一下代码:
接下来就到重点了,首先,我们的qsort函数是不知道需要排序的数据是什么类型的,所以我们需要结合形参width来访问数组base的内容,而base是void*类型的指针。我们假设传过来的base数组中的元素是整型类型,那么就可以用以下的方式访问:
那么回到my_qsort函数本身,if语句需要用来判断base数组中两个元素的大小,需要借助形参cmp函数指针,传入base数组中的两个数据,则有以下代码:
接下来就是最后一步了,实现swap函数,swap函数的难点也是我们不知道数据的类型,无法利用某个类型的中间变量进行直接交换。所以我们还是得一个字节一个字节的访问,交换,举个例子,如果我们需要交换两个整型变量,我们可以进行4次(int 类型占4字节)循环,每次交换一个字节即可。
请看代码:
void swap(const void* b1, const void* b2, int width)
{
//根据数据所占字节的大小设置循环次数
for (int i = 0; i < width; i++)
{
char tmp = *(char*)b1;
*(char*)b1 = *(char*)b2;
*(char*)b2 = tmp;
}
}
至此,我们已经完成了qsort函数的自实现啦
完整代码:
void swap(const void* b1, const void* b2, int width)
{
//根据数据所占字节的大小设置循环次数
for (int i = 0; i < width; i++)
{
char tmp = *(char*)b1;
*(char*)b1 = *(char*)b2;
*(char*)b2 = tmp;
}
}
void my_qsort(int* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
//有序数的边界(我们只需判断其左侧的无序数)
int limit = sz - 1;
//标记每一轮最后一次交换的位置
int last_change = 0;
//进行sz-1轮排序
for (int i = 0; i < sz - 1; i++)
{
//假设没有进行替换(数组已经有序)
int flag = 0;
//每一轮比较limit次,即只比较无序的数
for (int j = 0; j < limit; j++)
{
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//交换
swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
//如果交换了,则标记数组无序
flag = 1;
//记录最后一次交换的位置
last_change = j;
}
}
//每一轮结束后,更新有序数的边界,其边界就是上一轮最后一次交换的位置
limit = last_change;
//如果没有交换,则数组已经有序,直接跳出循环
if (0 == flag)
{
break;
}
}
}
my_qsort试用
我们试着给刚才的整型数组进行排序:
#define _CRT_SECURE_NO_WARNINGS 1
//日期:
//程序信息:
//程序员:
#include <stdio.h>
int cmp(const void* e1, const void* e2)
{
return (*(int*)e1) - (*(int*)e2);
}
void swap(const void* b1, const void* b2, int width)
{
//根据数据所占字节的大小设置循环次数
for (int i = 0; i < width; i++)
{
char tmp = *(char*)b1;
*(char*)b1 = *(char*)b2;
*(char*)b2 = tmp;
}
}
void my_qsort(int* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
//有序数的边界(我们只需判断其左侧的无序数)
int limit = sz - 1;
//标记每一轮最后一次交换的位置
int last_change = 0;
//进行sz-1轮排序
for (int i = 0; i < sz - 1; i++)
{
//假设没有进行替换(数组已经有序)
int flag = 0;
//每一轮比较limit次,即只比较无序的数
for (int j = 0; j < limit; j++)
{
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//交换
swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
//如果交换了,则标记数组无序
flag = 1;
//记录最后一次交换的位置
last_change = j;
}
}
//每一轮结束后,更新有序数的边界,其边界就是上一轮最后一次交换的位置
limit = last_change;
//如果没有交换,则数组已经有序,直接跳出循环
if (0 == flag)
{
break;
}
}
}
int main()
{
int arr[10] = {
71,114,105,62,44,47,93,80,56,121 };
int sz = sizeof(arr) / sizeof(arr[0]);
printf("排序前: ");
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
my_qsort(arr, sz, sizeof(arr[0]), cmp);
printf("\n排序后(升序): ");
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
程序运行结果如下:
给结构体排序,让结构体按照字符串的大小排序:
#include <stdio.h>
//定义结构体book
struct book
{
char name[10];
int price;
};
//判断字符串的大小
int cmp_str(const void* e1, const void* e2)
{
return strcmp((*(struct book*)e1).name, (*(struct book*)e2).name);
}
//打印结构体内容
void show_b(struct book b[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%s\n", b[i].name);
}
}
void swap(const void* b1, const void* b2, int width)
{
//根据数据所占字节的大小设置循环次数
for (int i = 0; i < width; i++)
{
char tmp = *(char*)b1;
*(char*)b1 = *(char*)b2;
*(char*)b2 = tmp;
}
}
void my_qsort(int* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
//有序数的边界(我们只需判断其左侧的无序数)
int limit = sz - 1;
//标记每一轮最后一次交换的位置
int last_change = 0;
//进行sz-1轮排序
for (int i = 0; i < sz - 1; i++)
{
//假设没有进行替换(数组已经有序)
int flag = 0;
//每一轮比较limit次,即只比较无序的数
for (int j = 0; j < limit; j++)
{
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//交换
swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
//如果交换了,则标记数组无序
flag = 1;
//记录最后一次交换的位置
last_change = j;
}
}
//每一轮结束后,更新有序数的边界,其边界就是上一轮最后一次交换的位置
limit = last_change;
//如果没有交换,则数组已经有序,直接跳出循环
if (0 == flag)
{
break;
}
}
}
int main()
{
struct book b[3] = {
{
"sanmao",50},{
"cpluse",30},{
"game",60} };
int sz = sizeof(b) / sizeof(b[0]);
qsort(b, sz, sizeof(b[0]), cmp_str);
show_b(b, sz);
return 0;
}
程序运行结果:
总结
自主实现qsort能够很大程度上提高我们对指针和数据在内存中的存储的理解。希望大家能够通过本篇博客有所收获。