冒泡排序
当我们遇到一组数列需要排序的时候我们通常会想到冒泡排序的方法,例如:如果我们需要将一组数进行升序排列的话,我们可以通过比较两个相邻数字的大小,如果前一个数大于后一个数,则它们不满足升序,我们需要将两个数位置互换,这样重复比较下一对相邻数的大小,如果不满足升序则互换,满足则不需要互换。如图:
当然,图上只是为了演示什么是冒泡排序假设的一种情况,我们可以把图上这样看成一趟,这样一趟走完之后,我们就可以把一个数换好,图上这一趟就把9换好了,那我们最多其实只需要8趟就能把所有数换好,让我们假设一个极端的情况:
我们还发现,每当我们完成一趟,需要比较的数也会少1个,因为会有一个数已经被排好,不用再比较,所以我们可以用嵌套循环,再建立临时变量实现两数的交换,代码如下:
void bubble_sort(int* a, int sz)
{
for (int i = 0; i < sz-1; i++)
{
int tmp = 0;
for (int j = 0; j < sz - 1 - i; j++)
{
if (a[j] > a[j + 1])
{
tmp = a[j];
a[j] = a[j + 1];
a[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 4,5,8,3,9,1,7,2,6 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
qsort函数
冒泡排序具有一定的局限性,我们在比较大小的时候,用的是大于或者小于号,但是如果数据类型是字符或者结构体的时候,我们就不能使用大于或者小于号来比较,这个时候我们就可以使用库函数qsort来排序。qsort函数的原型如图:
qsort函数需要四个参数,依次是待排序数组首元素的地址、待排序数组的元素个数、元素的大小、比较函数的函数指针。那我们使用qsort函数来进行一个简单的数组排列:
#include<stdlib.h>
int cmp_int(const void*p1, const void* p2)//比较函数
//void*是无具体类型的指针,所以需要强制类型转化才可以比较大小
{
//qsort函数的返回值规定是:
//p1>p2 返回大于0的数;p1<p2 返回小于0的数;p1=p2 返回0
//所以我们做差也可以得到同样的效果
return *(int*)p1 - *(int*)p2;
}
int main()
{
int arr[] = { 2,5,8,9,7,6,3,4,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);//最后一个参数是比较函数的函数指针
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
如果需要排序的是结构体呢?
struct Stu//学生
{
char name[20];//学生名字
int age;//学生年龄
}
如果我们要给图上的结构体排序,有两种排序方法,按学生名字排序或者按学生年龄排序,结构体成员访问操作符有:
//结构体变量.成员名
//结构体指针->成员名
和整型数组同理,这样我们就可以对结构体进行排序。
按学生年龄排序:
#include<stdio.h>
#include<stdlib.h>
struct Stu {
char name[20];
int age;
};
int cmp_by_age(const void*p1, const void* p2)//年龄比较函数
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
int main()
{
struct Stu s[] = { {"zhangsan",14},{"lisi",13},{"wangwu",15} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_by_age);
for (int i = 0; i < sz; i++)
{
printf("%s %d ", s[i].name, s[i].age);
}
return 0;
}
按学生姓名排序,需要用到比较字符串大小的库函数(strcmp),按字符串对应字符的ASCII码值比较大小:
#include<string.h>
struct Stu {
char name[20];
int age;
};
int cmp_by_name(const void* p1, const void* p2)//名字比较函数
{
return strcmp(((struct Stu*)p1)->name , ((struct Stu*)p2)->name);
}
int main()
{
struct Stu s[] = { {"zhangsan",14},{"lisi",13},{"wangwu",15} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]),cmp_by_name);
for (int i = 0; i < sz; i++)
{
printf("%s %d ", s[i].name, s[i].age);
}
return 0;
}
qsort函数的模拟实现
现在我们已经对冒泡和qsort函数都有一个了解了,那我们能否用冒泡排序的方式模拟实现qsort函数呢?
如果我们要用冒泡排序的方式模拟qsort函数,我们要延用冒泡排序的思想,就是相邻两数比较然后交换位置,但是这时我们不能确定需要交换的是数还是字符,我们也无法简单的用大于或小于号来比较大小,所以我们要把原来的数字抽象为一块空间,当我们交换这两块空间时,存储在空间里的数据也会被交换,同时我们也可以通过解引用指针来比较相应数据的大小。
首先我们先定义形参,需要的依次是待排序数组首元素的地址(void *base)、待排序数组的元素个数(count)、元素的大小(size)、比较函数的函数指针(int(*cmp(void const*p1,void const*p2))。
bubble_sort(void *base,int count,int size,int(*cmp(void const*p1,void const*p2)
如果我们要将数抽象为空间,那我们需要改变的就是原冒泡排序中比较和交换的两部分。(如图)
if (a[j] > a[j + 1])//比较
{
tmp = a[j];//交换
a[j] = a[j + 1];
a[j + 1] = tmp;
}
那我们先来改变比较数据这一步,对于不同的数据,我们还是像qsort函数一样,需要提供一个专门用来比较数据的函数,就像对于结构体,我们的比较函数有cmp_by_name和cmp_by_age,这就是比较数据的方法,只是我们把比较数据的方法封装成了一个函数。
int cmp_by_age(const void*p1, const void* p2)//结构体年龄比较函数
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
int cmp_by_name(const void* p1, const void* p2)//结构体姓名比较函数
{
return strcmp(((struct Stu*)p1)->name , ((struct Stu*)p2)->name);
}
int cmp_int(const void* p1, const void* p2)//整形数据比较函数
{
return *(int*)p1 - *(int*)p2;
}
再就是原冒泡排序交换的这一步,在我们交换数组内两数据的时候,我们首先要拿到数组首元素的地址,首元素的地址加上元素的长度我们就能得到下一元素的地址,再将两个地址交换,这样我们就可以成功的将两元素互换,用整型数组模拟一下交换:
也就是首元素地址base+数据长度size就能得到下一元素的地址,因为数据会有字符串,所以我们用强制类型转换为char*型指针交换,这样无论是什么类型的数据都可以一个字节一个字节交换,再添加一个循环我们就可以依次交换数组中所有元素。代码如下:
void swp(void*p1,void*p2,int size)
{
for (int i = 0; i < size; i++)
{
char tmp = *((char*)p1 + i);
*((char*)p1 + i) = *((char*)p2 + i);
*((char*)p2 + i) = tmp;
}
}
void bubble_sort(void* base, int count, int size, int(*cmp)(const void *,const void*))
{
for (int i = 0; i < count - 1; i++)
{
for (int j = 0; j < count - i - 1; j++)
{
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size)>0)
{
swp((char*)base + j * size, (char*)base + (j + 1) * size,size);
}
}
}
}
完整代码实现如下:
#include<string.h>
struct Stu {
char name[20];
int age;
};
int cmp_by_age(const void*p1, const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
int cmp_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name , ((struct Stu*)p2)->name);
}
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
void swp(void*p1,void*p2,int size)
{
for (int i = 0; i < size; i++)
{
char tmp = *((char*)p1 + i);
*((char*)p1 + i) = *((char*)p2 + i);
*((char*)p2 + i) = tmp;
}
}
void bubble_sort(void* base, int count, int size, int(*cmp)(const void *,const void*))
{
for (int i = 0; i < count - 1; i++)
{
for (int j = 0; j < count - i - 1; j++)
{
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size)>0)
{
swp((char*)base + j * size, (char*)base + (j + 1) * size,size);
}
}
}
}
int main()
{
struct Stu arr[] = { {"zhangsan",14},{"lisi",13},{"wangwu",15} };
//int arr[9] = { 9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
//bubble_sort(arr, sz, sizeof(arr[0]), cmp_by_age);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_by_name);
/* bubble_sort(arr, sz, sizeof(int), cmp_int);*/
//for (int i = 0; i < sz; i++)
//{
// printf("%d ", arr[i]);
//}
for (int i = 0; i < sz; i++)
{
printf("%s %d ", arr[i].name, arr[i].age);
}
return 0;
}
按名字排序
按年龄排序
整型数组排序
回调函数
回调就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
在使用bubble_sort模拟qsort函数的时候我们会发现,形参表的最后一个参数是一个函数指针,而且这个函数指针能根据我们传过去的参数类型调用相应的比较函数,这就是回调函数。
void bubble_sort(void* base, int count, int size,
int(*cmp)(const void *,const void*))
//比较函数的函数指针,通过这个指针调用的函数是回调函数,也就是比较函数是回调函数
{
for (int i = 0; i < count - 1; i++)
{
for (int j = 0; j < count - i - 1; j++)
{
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size)>0)
{
swp((char*)base + j * size, (char*)base + (j + 1) * size,size);
}
}
}
}