首先初步认识一下
qsort 是一个库函数,包含在头文件stdlib.h中
作用:基于快速排序算法实现的一个排序函数
接下来认识这个函数所需要的参数有四个。接下来分别进行分析。
首先我们发现数据起始位置的指针用的是void*来接受,为啥呢???
因为我们不知道我们将来比较的数据类型为什么,有可能是int* 或者char* 又或者是结构体指针,所以我们在这放置了一个void*来接受,void*可理解为通用类型指针,可能就有人问了,既然有这个万金油的指针类型,那我们还要其他指针类型干什么??下面给出了原因。
我们发现我们自己需要定义一个比较函数,参数类型为两个指针变量,返回类型为int 。
但是为什么这里的指针类型需要定义为void* 类型呢???
这里就需要我们明白指针类型的意义。
指针类型其实决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
void* 可以接受任意类型的指针,但是确定也比较明显,就是在对指针解引用操作的时候,计算机不知道它有多大的权限(可以向后访问几个字节)。
由于我们并不知道我们所要比较的元素是哪种类型的,就采用空类型来接受。而在比较的时候,我们需要将其强制转化成比较的类型,方可进行比较。具体请看下面代码展示。
对这个函数有一个初步了解之后,我们开始尝试使用这个函数。
int cmp_int(const void* e1, const void* e2)
{
if (*(int*)e1 > *(int*)e2)
return 1;
else if (*(int*)e1 < *(int*)e2)
return -1;
else
return 0;
//return *(int*)e1 - *(int*)e2;
//这种方式其实更好,利用前者减去后者,差返回就好。
//这样刚好满足这个函数的定义
//e1>e2 返回 大于0 <==> e1-e2>0
//e1<e2 返回 小于0 <==> e1-e2<0
//e1=e2 返回 0 <==> e1-e2=0
}
void my_print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(arr+i));
}
}
int main()
{
int arr[] = { 10,9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
my_print(arr, sz);
return 0;
}
在这个过程中,我们需要比较的是int类型的元素,故将额e1和e2强制类型转换成int* 。
然后我们用qsort函数排序一下结构体。
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
void test3()
{
struct Stu arr[3] = { {"zhangsan", 20, 55.5},{"lisi", 30, 88.0},{"wangwu", 10, 90.0} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
int main()
{
test3();
return 0;
}
经过排序后的结果如下图:
我们发现年龄有原来的无序变为升序。
可以得出一个结论:利用qsort函数可以将一个无序的元素变为升序。那这里有人好奇了,那怎样才能变为降序呢???
其实我们只需要改变逻辑即可!原来我们创建了一个比较函数,其逻辑为
我们颠倒逻辑改为
这样原来是升序,逻辑转换一下,就变为将序了。
经过前面的解析,已经初步明白了qsort函数的使用,我们发现qsort可以排列各种类型的数据,
那我们可以使用冒泡排序进行一个模拟,使我们的冒泡排序也可以万能呢?
首先观察一下我们原有的冒泡排序
void bubble_sort(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz-1-i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
print_arr(arr,sz);
return 0;
}
发现要使冒泡排序可以排序任何元素,要进行一些改动。
经过初步的分析,我们发现我们的冒泡排序还缺少某些参数。例如每个元素的宽度,如若知道这个整体的起始地址,再知道每个元素的宽度和元素的个数。
我们注意到,一旦冒泡排序的元素个数确定了,冒泡排序的趟数也就确定了。
其次,我们发现不同元素之间的比较方式不同,所以我们应该将比较方式提取出来,不同的数据类型采用不同的比较方式。所以就个人自行定义比较方式。
所以我们 的冒泡排序的整体大致为
void bubble_sort(void* base, int num, int width, int (*cmp)(const void*e1, const void*e2))
//base为数据的起始地址
//num为元素的个数
//width为每个元素的大小(字节)
//cpm为比较函数
由于我们无法提前得知比较类型的数据,故我们的比较函数的指针类型也应该为void*,以便存放各种类型的指针。
开始模拟
先写出整体的大致思路,然后进行修改。
//冒泡排序
void my_qsort(void* base, int num, int width, int(*com_int)(const void* e1, const void* e2))
{
int i = 0;
int j = 0;
for (i = 0; i < num - 1; i++)
{
for (j = 0; j < num - 1 - i; j++)
{
/*
*if(>0)
*swap
*
*这里就是需要我们进行改变的。
*
*/
}
}
}
int main()
{
int arr[10] = { 10,9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
my_qsort(arr, sz, sizeof(arr[0]), com_int);
//打印数组
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
我们思考如何应该如何让判断当前两个元素的大小???
先写出我们的一个比较函数
首先观察我们原来的比较函数,如若前者大于后者,则进行交换。
类比与这个,我们可以写出
//比较函数
int com_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
如果cmp-int(,)大于0,则前面的数据大于后面的数据,则进行交换。
如果cmp-int(,)小于或者等于0,则前面的数据小于后面的数据,则不进行交换。
if(cmp(,)>0)
{
//交换;
}
我们发现这个比较函数需要那两个待比较元素的首地址,可是我们该如何拿到这两个元素的首地址呢????
我们需要计算出待比较元素的地址
如何计算呢??
我们以整形数组为例进行演示:
那么如何寻找相邻的待比较元素的地址呢??
再看一下这张图,我们来分析一下。其实这个过程中,相邻元素的下表一个是就 j ,一个是 j+1 。
故
//arr[j] <==>(char*)base + width*j
//arr[j+1] <==>(char*)base + width*(j+1)
所以判断函数可以写成
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
/*
*
* 此处是交换函数;
*
*/
}
现在我们来编写交换函数swap
首先交换函数需要传输 待交换元素的地址 , 如果传输 待交换元素的值 ,无法达到交换的效果。但是需要注意的是我们传输过去的是一个元素的首地址(char*),但是我们不知道一个元素有几个字节(就是一个元素的宽度)?我们知道,交换元素意味着元素的整体都进行交换,而不能只交换部分。故我们也应该将宽度(大小)传过去。
if (com_int((char*)a + j * width, (char*)a + (j + 1) * width)>0)
{
swap((char*)a + j * width, (char*)a + (j + 1) * width, width);
}
以整形举例,方便大家理解这个交换过程。
由于 swap的参数类型为cahr* 类型,一次只能交换一个字节,故需要交换width(即元素的宽度或者大小)次 。故函数可以写为:
void swap(char* p1,char*p2,int width)
{
for (int n = 0; n < width; n++)
{
char tmp = *(p1 + n);
*(p1 + n) = *(p2 + n);
*(p2 + n) = tmp;
}
}
通过单个字节的交换,实现元素的交换。
最终实现了利用冒泡排序实现qsort万能排序的功能。整体代码在下
//比较函数
int com_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void swap(char* p1,char*p2,int width)
{
for (int n = 0; n < width; n++)
{
char tmp = *(p1 + n);
*(p1 + n) = *(p2 + n);
*(p2 + n) = tmp;
}
}
void my_qsort(void* base, int num, int width, int(*com_int)(const void* e1, const void* e2))
{
int i = 0;
int j = 0;
for (i = 0; i < num - 1; i++)
{
for (j = 0; j < num - 1 - i; j++)
{
if (com_int((char*)a + j * width, (char*)a + (j + 1) * width)>0)
{
swap((char*)a + j * width, (char*)a + (j + 1) * width, width);
}
}
}
}
int main()
{
int arr[10] = { 10,9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
my_qsort(arr, sz, sizeof(arr[0]), com_int);
//打印数组
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}