一、冒泡排序
void sort(int arr[], int sz)
{
//这是一个冒泡排序的函数
int i, j;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
}
这是一个常见的冒泡排序的函数。
仅仅只能对整型类数据进行排序。C语言库函数中提供了qsort函数,可以对任意类型的数据进行排序。
二、qsort函数的简单使用
qsort函数是C语言自带的库函数,在使用他之前,首先需要了解,需要给他传几个参数,以及这几个参数分别对应的类型是什么
https://zh.cppreference.com/w/c/algorithm/qsort
从这里可以看出,在使用qsort函数的时候,需要传四个参数进去。
1. 一个void*类型的指针,指向待排序数组的首元素
2.所求数组的元素个数
3.所求数组元素的大小
4.一个函数指针(指向用于比较数组元素的函数)
这里略微说一下自己的拙见
①.void*类型指针
因为qsort函数要实现可以对任意数据类型进行排序,所以这里用void*类型指针进行接收。
②int(*comp)(const void * p1,const void*p2)
指向一个数组元素对应数据类型的比较函数,返回值的规律为
return | *p1,*p2比较 |
>0的值 | *p1>*p2 |
<0的值 | *p1<*p2 |
0 | *p1 == *p2 |
简单应用
Ⅰ、整型数组的排序
这是一段简单使用的代码,以排序一个整形数组为例。
在这里,前三个参数已经搞定,需要解决的就是第四个参数。
int arr[] = { 3,12,35,8,9,6,59,0,8, };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
那么就来写一个比较int类型的函数
因为qsort函数参数中这个指针是用的这种形式,所以我们写的cmp_int 函数的形式为
int cmp_int(const void* p1, const void* p2)
这里需要注意
int cmp_int(const void* p1, const void* p2)
{
if(*p1 > *p2)
}
不能这样写,因为传参类型是void*,不能直接解应用, 将指针类型强制转换成需要进行比较的指针类型进行比较
int cmp_int(const void* p1, const void* p2)
{
if (*(int*)p1 > *(int*)p2)
return 1;
else if (*(int*)p1 == *(int*)p2)
return 0;
else if (*(int*)p1 < *(int*)p2)
return -1;
}
这样写虽然可以,但是代码会显得比较冗余,可以进行简化。
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
直接返回两数做差的值,效果相同还更加简洁。
完整代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p2 - *(int*)p1;
}
int main()
{
int arr[] = { 3,12,35,8,9,6,59,0,8, };
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;
}
测试一下
可见,数组是按照升序排布,那如果想按照降序排的话,只需要将cmp_int函数中判断逻辑稍作更改即可
如下
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p2 - *(int*)p1;
}
int main()
{
int arr[] = { 3,12,35,8,9,6,59,0,8, };
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;
};
为了更加直观的看出比较的结果,在定义的结构体变量Stu中我们只定义age与name两个成员。
结构体变量进行比较时,使用结构体中的一成员进行比较,根据需求进行选择
int cmp_Stu_by_age(const void *p1,const void *p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
int cmp_Stu_by_name(const void *p1,const void *p2)
{
return strcmp(((struct Stu*)p1)->name,((struct Stu*)p2)->name);
}
用age判断的逻辑与cmp_int的逻辑相同,只是这里强制转换为结构体变量类型
用name判断则是使用到了strcmp这个字符串比较函数。
整体代码如下
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
int cmp_stu_by_age(const void* p1, const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void test_stu_by_name()
{
struct Stu arr[3] = { {"zhangsan",20},{"lisi",19},{"wangwu",35} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i].age);
}//这里只是测试写的函数是否可用,以上帝视角进行观察,就只用把age打印出来就知道顺序有没有更换了
}
int main()
{
test_stu_by_name();
return 0;
}
测试一下
三、qsort函数的简单模拟实现
#include<stdio.h>
#include<stdlib.h>
void sort(int arr[], int sz)
{
//这是一个冒泡排序的函数
int i, j;
for (i = 0; i < sz; i++)
{
for (j = 0; j < sz - i; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
}
基于这个冒泡排序函数模拟qsort函数的功能
void bubble_sort(void *base,int sz,int width,int (*cmp)(const void *p1,const void *p2))
这里参考qsort的参数类型进行改造
因为要实现可以进行任何数据类型的比较,所以用void*指针接收数组的第一个元素
要确定一个数组元素大小是多少,定义一个参数是width
比较过程用一个函数指针接收一个函数,什么数据类型比较传对应的比较函数
这里我们对内部进行改造,比较趟数上没有什么大毛病,需要进行更改的地方就是比较过程与交换过程
void bubble_sort(void *base,int sz,int width,int (*cmp)(const void *p1,const void *p2))
{
int i, j;
for (i = 0; i < sz - 1; 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);
}
}
}
}
这里因为使用void*类型接收的数据,所以不能确定一个元素到底有多大,需要手动传一个元素的大小过去,就是width。
最关键的一点在于,如何给cmp这个比较函数传参
arr[j] arr[j+1]
原来的比较是这样进行比较,但是在改造的过程中已经没有arr这个变量了,只剩下了base这个指针变量
那可以直接用base + j ,base + j + 1这种表示形式嘛?
很明显不行
base是个void*指针,不能直接进行计算,那么这时候,width就派上用场了,因为这里大小我们使用sizeof进行计算,单位是字节,那么我们就直接将base强制类型转换为char*类型,就可以直接跳过对应的宽度。
改写为(char*)base + j *width,(char*)base +(j + 1) * width。
对应的比较函数就不多做赘述了,与上文相似。
接下来就是交换数组中的元素顺序
这种交换方式比较局限,将交换过程写进一个函数Swap中
void Swap(char *p1,char *p2,int wid)
{
for (int i = 0; i < sz; i++)
{
char temp = *(p2 + i);
*(p2 + i) = *(p1 + i);
*(p1 + i) = temp;
}
}
在cmp中已经强制类型转换为char*类型,这里Swap的参数也采用char*类型
需要注意的是,这里只传起始地址是不够的,需要知晓到底要交换多少个字节,需要传入比较元素的大小
如图,如果不传入wid,那么以整型为例,整型大小为4个字节,只交换起始地址显然不会完全交换。
整体代码如下
#include<stdio.h>
#include<stdlib.h>
void sort(int arr[], int sz)
{
int i, j;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - i; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
}
void Swap(char *p1,char *p2,int wid)
{
for (int i = 0; i < sz; i++)
{
char temp = *(p2 + i);
*(p2 + i) = *(p1 + i);
*(p1 + i) = temp;
}
}
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
void bubble_sort(void *base,int sz,int width,int (*cmp)(const void *p1,const void *p2))
{
for (i = 0; i < sz - 1; 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);
}
}
}
}
void test()
{
int arr[] = { 3,12,35,8,9,6,59,0,8, };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
for (int i = 0; i < sz; i++)
printf("%d ", arr[i]);
}
int main()
{
test();
return 0;
}
测试一下
再测试一下结构体
void Swap(char* p1, char* p2, int sz)
{
for (int i = 0; i < sz; i++)
{
char temp = *(p2 + i);
*(p2 + i) = *(p1 + i);
*(p1 + i) = temp;
}
}
void bubble_sort(void* base, int sz, int width, int (*cmp)(const void* p1, const void* p2))
{
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);
}
}
}
}
void sort(int arr[], int sz)
{
//这是一个冒泡排序的函数
int i, j;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
}
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test_stu_by_name()
{
struct Stu arr[3] = { {"zhangsan",20},{"lisi",19},{"wangwu",35} };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i].age);
}//这里只是测试写的函数是否可用,以上帝视角进行观察,就只用把age打印出来就知道顺序有没有更换了
}
int main()
{
test_stu_by_name();
return 0;
}