冒泡排序有局限性,实现时间长而且只能进行整型数据的排序,接下来介绍模拟实现qsort来方便实现各种数据的排序。
函数基本形式:
可以看到该函数有四个参数,第四个参数是一个函数指针,这个指针指向的函数第一个参数和第二个参数都是const void*,所以之后要调用这个函数的时候,第四个参数应该传一个函数的地址过去。base是一个指针,指向的是待排序数组的第一个元素,num是base指向的元素的个数,size是base指向待排序元素数组的大小。在冒泡排序中,由于类似与结构体这样的数据不能直接比较大小,所以冒泡排序只能比较整型数据,那么现在就可以统一写一个函数,把两个元素的比较方法封装成函数,然后把函数的地址传给qsort函数中的函数指针,也就是第四个变量。如果传过来的是整型就按照整型的方式来比较,如果是字符串就按照字符串的方式来比较,如果是结构体就按照结构体的方式来比较。qsort函数也是用这个思想,所以这第四个参数就是指向这个比较函数的指针。
使用方式:
现在就要写这个比较函数。我已经明确知道自己要排序整型,所以我就写一个比较整型大小的函数就行了,而且这个函数的两个参数要和qsort函数里面那个函数指针所指向的函数的参数类型一致,也就是const void*。这个函数就大概长这样:
然后这个函数的指针就应该是待比较两个元素的地址。然后qsort还有要求,这两个指针指向的元素所比较出来的结果,如果p1>p2,返回一个大于0的数字,如果p1<p2,返回一个小于0的数字,如果p1=p2,返回0。另外,这个比较函数在编译比较代码时并不能直接去解引用然后比较,因为void*类型的指针是无具体类型的指针,不能直接解引用,也不能进行加减乘除的运算。但是我们在写这个函数时是非常肯定指针指向的类型是整型,所以我希望从p1、p2这两个位置向后找到整型数据,那么直接强制类型转换就好了。比较函数大概就这样:
综上,代码完成再打印一下:
#include <stdio.h>
#include <stdlib.h>
void Print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int com_int(const void* p1, const void* p2)
{
if (*(int*)p1 > *(int*)p2)
{
return 1;
}
if (*(int*)p1 < *(int*)p2)
{
return -1;
}
if (*(int*)p1 = *(int*)p2)
{
return 0;
}
}
int main()
{
int arr[] = { 10,2,3,6,7,4,8,9,5,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), com_int);
Print_arr(arr, sz);
return 0;
}
接下来使用qsort排序一下结构体数据:首先创建一个结构体变量并创建一个结构体数组用来存放结构体元素:
struct Stu
{
char name[20];
int age;
};
void test2()
{
struct Stu arr[3] = { {"zhangsan",20},{"lisi",35},{"wangwu",18} };
}
然后就是利用qsort来排序,前面三个正常传参,关键是第四个参数,第四个参数。此时用qsort函数排序的是结构体数据,那么就是传结构体元素比较的函数。问题来了,怎么比较两个结构体的大小?这个结构体有两种数据,一种是名字,一种是年龄,所以可以按照名字来排序,也可以按照年龄大小来排序。首先按照名字来排序的话:
现在又是和上面一样的问题,不能直接比较大小,而且p1和p2接收的结构体类型的地址,所以它是个结构体类型的指针,所以要强制类型转换为结构体类型的指针,然后字符串的比较用strcmp函数来比较 ,而且这个函数的返回值也是>0、<0、=0的,所以可以直接返回。最后把这个比较函数的函数名传给qsort就好了。补充一点:strcmp比较的是字符串每个字符对应位置的ASCII码值的大小,例如abcdef和abd,前面两个a对应a,b对应b一样大,所以就接下去比较,d的ASCII码值比c的大,所以abd>abcdef。最后代码的样子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Stu
{
char name[20];
int age;
};
int com_stu_by_name(void* p1, void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test2()
{
struct Stu arr[3] = { {"zhangsan",20},{"lisi",35},{"wangwu",18} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), com_stu_by_name);
}
int main()
{
//test1();
test2();
return 0;
}
最后调试一下看看结果:
可以说非常的完美。
接下来按照年龄来排序,那么上面这个函数就用不了了,就得重写一个新的函数,那么就只需要把结构体里面的年龄元素做差即可:
完美。以上就是qsort的使用。
接下来就是模仿qsort函数来实现一个利用冒泡排序的方式排序任意数据的函数。回顾一下冒泡排序的写法:
void bubble_sort(int arr[], int sz)
{
int i = 0, j = 0;
for (i = 0; i < sz; i++)
{
for (j = 0; j < sz - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
首先如果想要排序其他数据,那么函数的第一个参数,这个数组就不可以是整型变量。对比一下qsort函数,它的参数是void*类型的,是无具体类型的指针,可以接收任意类型的地址,这个地址是排序元素的起始地址。那么可以模仿qsort也写一个void*的指针。在排序时还得知道元素的个数,元素个数也不会是负数,所以用size_t去定义。有了元素个数,但是并不知道元素的大小,就是多少个字节,所以又需要第三个变量来得知元素大小。最后,不同的数据比较方法也不一样,那么可以抽象成一个函数指针,这个指针指向的函数有两个参数,指向待比较的两个元素的地址,返回类型是int。修改一下就是:
接下来就是冒泡排序函数的内部。首先就是比较两个元素之间大小关系时,数据不同比较方式不同,但是有了第四个参数的函数指针,就可以利用这个指针来进行大小的判断。此时这个指针指向那个比较函数,那么使用这个指针相当于调用这个函数,那么就需要把需要排序的两个值传个这个函数,如果返回值是>0的,交换,以此类推,接下来就是要把两个元素的地址传给这个函数,这一步非常关键。
假设有个数组叫arr[ ],在比较时,第一次可能比较arr[1]和arr[2],第二次可能比较arr[3]和arr[4],但是对于这个冒泡排序函数来说,没有所谓的arr[0]或[1]的概念,只有起始位置void* base,那么就还需要进行指针的运算以此到达下一个元素,此时第三个参数就起作用了,假设数据是整型的,第三参数是4,我此时想跳过四个字节从arr[0]到arr[1],那么应该是base+4,为了保证它确实跳过四个字节,可以把base强制类型转换为char*类型的指针,如果要跳到arr[2],就应该加8,也就是4*2,抽象一下就是要跳过j个字节,就是j*width。所以原先冒泡排序内部if语句就可以写成:
接下来就是交换元素:交换的元素是上面传过去的地址指向的元素,可以考虑再写一个Swap函数来完成交换,就直接把那两个地址传过去最简单。由于传过去的值已经被强制类型转换为char*的指针了,所以Swap的两个形参就可以直接写成char*类型的。但是仅仅把这两个地址传过去貌似不够,因为是要交换这两块空间的内容的,但是没有说明交换空间的长度,这样可能交换不完全或者换错了。然后就写个循环,把整块空间的地址给换了,整型就四个字节算整块,字符型就一个字节算整块。所以循环的次数要小于width就是字节宽度。
总结一下,代码就是这样:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Swap(char* buf1, char* buf2,size_t,width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{
int i = 0, j = 0;
for (i = 0; i < sz; i++)
{
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);
}
}
}
}
很难这个,吸收消化,多点耐心。