前言:内容包括使用库函数qsort排序任意类型的数据,模拟实现qsort函数(冒泡排序的逻辑)
我们先了解qsort函数的语法:qsort函数默认按照升序排序数据
void qsort (void* base, size_t num, size_t size,
int (*compar)(const void*,const void*));
qsort函数的头文件:
#include<stdlib.h>
qsort函数有4个参数:
第一个参数 void*base:指向待排序数组的第一个元素的指针
第二个参数 size_t num:待排序数组的元素个数
第三个参数 size_t size: 待排序数组的每个元素的所占空间的大小,单位是字节
第四个参数 int (*compar)(const void*,const void*):指向一个能够比较两个元素大小的函数,即一个函数指针,此函数需要使用者自己设计
由于设计qsort函数的人不知道 未来会是谁使用qsort函数排序哪种类型的数据,故而设计void*指针,从而能够接收任意类型数据的地址
比较两个元素大小的函数的设计规则:
int (*compar)(const void*,const void*))
此函数的返回值:
返回值 | 意义 |
---|---|
<0 | 第一个元素<第二个元素 |
0 | 第一个元素=第二个元素 |
>0 | 第一个元素>第二个元素 |
part 1:qsort函数排序整型数据
#include<stdio.h>
#include<stdlib.h>
void Print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2; //升序
//将指针p1和p2强制类型转换成int*类型,然后对其解引用
// *(int*)p2 - *(int*)p1 降序
}
int main()
{
int arr[10] = { 5,7,3,9,1,4,6,2,8,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
Print(arr, sz);
return 0;
}
由于qsort函数默认是升序排列的,若是想要降序排列,只需将p1的位置写成p2,p2的位置写成p1
void*的指针不能直接解引用,和++,--操作,
需要将void*类型转成你需要的类型
part 2:qsort函数排序浮点型数据
#include<stdio.h>
#include<stdlib.h>
void Print(float arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%f\n", arr[i]);
}
}
int cmp_flo(const void* p1, const void* p2)
{
if (*(float*)p1 - *(float*)p2 > 0.000000)//升序
{
return 1; // 第一个元素大于第二个元素
// 降序:*(float*)p2 - *(float*)p1 > 0.000000
}
else if (*(float*)p1 - *(float*)p2 < 0.000000)
{
return -1; //第一个元素小于第二个元素
// 降序:*(float*)p2 - *(float*)p1 < 0.000000
}
else
{
return 0; //第一个元素等于第二个元素
}
}
int main()
{
float arr[] = { 1.2f,1.1f,1.8f };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_flo);
Print(arr, sz);
return 0;
}
降序排列需要将所有的p1写成p2,p2写成p1
part 3:qsort函数排序字符串
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int cmp_str(const void* p1, const void* p2)
{
return strcmp((char*)p1, (char*)p2);//升序
//降序:strcmp((char*)p2, (char*)p1)
}
int main()
{
char arr[] = "ceadb";
int len = strlen(arr);
qsort(arr, len, sizeof(arr[0]), cmp_str);
puts(arr); //结果是abcde
return 0;
}
part 4:qsort函数排序字符串数组
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void Print(char* arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s\n", arr[i]);
}
}
int cmp_by_len(const void* p1, const void* p2)
{
return strlen(*(char**)p1) - strlen(*(char**)p2);//升序
//降序:strlen(*(char**)p2) - strlen(*(char**)p1)
}
int main()
{
char* arr[] = { "san","yi","ling" };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_by_len);
Print(arr, sz);
return 0;
}
排序按照字符串的长度排序
一级指针数据的地址需要二级指针来接收,故而需要将指针p1和p2强制转换成二级指针(char**)
part 5 qsort函数排序结构体数据
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{
char name[10];
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 main()
{
struct Stu s[] = { {"zhangsan",30},{"lisi",15},{"wangwa",20} };
int sz = sizeof(s) / sizeof(s[0]);
/*qsort(s, sz, sizeof(s[0]), cmp_by_age);*/
qsort(s, sz, sizeof(s[0]), cmp_by_name);
return 0;
}
结构体数据类型的排序需要首先确定按照什么来排序,这里设定一个学生类型包括:名字,年龄
故而我们有两种排序规则:其一:按照名字来排序,其二:按照年龄来排序
part 6:运用冒泡排序思维模拟实现qsort函数
void Swap(char* buf1, char* buf2,int 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*, const void*))
{
size_t i = 0;
for (i = 0; i < sz - 1; i++)
{
size_t j = 0;
int flag = 1;
for (j = 0; j < sz - 1 - i; 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 = 0;
}
}
if (flag == 1)
{
break;
}
}
}
我设定模拟qsort函数功能的函数名为bubble_sort,按照升序排列数据
冒泡排序我就不详解了
base指针:指向待排序数组的第一个元素
sz:无符号数(即正数),代表待排序数据的个数
width:一个数据所占内存空间的大小,单位是字节
cmp指针:指向一个由使用者设计的能够比较两个元素大小的函数,即cmp是一个函数指针
flag:标记某一趟排序的数组是否已经有序
1 sz个数据,共需进行sz-1趟冒泡排序
2 一趟冒泡排序内部:
首先都假设此趟数据已经有序,flag=1,若是发生了交换,则将flag置为0,一趟冒泡排序结束后判断flag的值,若是flag仍为1,说明在这一趟冒泡排序中没有数据发生交换,则表明此组数据已经有序,结束循环,若是flag变成0,说明这一趟冒泡排序由数据发生交换,表明此组数据还未有序,继续下一趟冒泡排序
a. 若是j为下标的元素>j+1为下标的元素,则需要交换
交换:1 若是cmp函数(比较两个元素的大小)返回值>0:第一个元素大于第二个元素,交换
2 使用char*指针,将两个元素一个字节一个字节地完成交换,这样能够交换任意类型的数据
将指向首元素的指针base强制转换成char*类型,这样一次能够跳过一个字节
可能会有疑惑为什么不降base指针转成其他类型的指针,比如int*
因为只有char*能够一次跳过一个字节,这样对于任意类型的数据都能进行操作
要是转成int*类型的指针,一次能够跳过4个字节,不能够随意对非整型的数据进行操作
(char*)base + j * width, (char*)base + (j + 1) * width
base指针跳过j个大小为width字节的元素
base指针跳过(j+1)个大小为width字节的元素
可以联想普通的冒泡排序交换数组中两个数据的方法:交换arr[j], arr[j+1]
Swap函数完成两个元素每一个字节的交换
void Swap(char* buf1, char* buf2,int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
比如数字3和数字4进行交换:
若是小端存储:
03 00 00 00 04 00 00 00
buf1指向03 buf2指向04,交换完一对字节后,分别++,指向下一对要交换的字节
对应的颜色进行交换 03和04交换……
下面是使用模拟实现qsort函数功能的函数排序整型数组:
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;//这是升序
//降序:*(int*)p2- *(int*)p1
}
// cmp_int 函数需要我们自己设计
void Print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
void Swap(char* buf1, char* buf2,int 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*, const void*))
{
size_t i = 0;
for (i = 0; i < sz - 1; i++)
{
size_t j = 0;
int flag = 1;
for (j = 0; j < sz - 1 - i; 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 = 0;
}
}
if (flag == 1)
{
break;
}
}
}
int main()
{
int arr[10] = { 3,6,1,7,2,8,10,9,4,5 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
Print(arr, sz);
return 0;
}
当然你也可以使用自己模拟实现的qsort函数去排序其他类型的数据,比较不同类型元素大小的函数在上方都有