目录
什么是qsort?
qsort是 C 语言标准库<stdib.h>中的一个函数,用于对数组进行快速排序,如下
base:指向需排序的数组指针(同数组首元素地址)
。num
:数组中的元素个数。size
:每个元素的大小(以字节为单位)。compar
:比较函数指针,用于定义排序顺序。
函数原型
void qsort(void *base, size_t num, size_t size, int (*compar)(const void *, const void *));
比较函数 compar
比较函数 compar 负责定义数组元素的排序顺序。它应该接受两个参数,每个参数的类型都是 const void *,并返回一个整型值。比较函数的返回值定义了元素之间的排序关系:
- 如果返回值小于 0,则第一个参数应该排在第二个参数之前。
- 如果返回值等于 0,则两个参数的相对位置不变。
- 如果返回值大于 0,则第一个参数应该排在第二个参数之后。
排序整型数组
void是无具体类型的指针,可以接受任意类型的地址,但是不能解引用,也不能+-操作,因为它不知道自己有几个字节的权限
int a = 10;
void *pv = &a;
compare函数形参中:因为指针类型是void,不能解引用,需要强制类型转换为int再解引用(根据自己的数据类型来决定是int还是char还是结构体)
#include <stdio.h>
#include <stdlib.h>
int compare(const void* a, const void* b) {
return (*(int*)a - *(int*)b);
}
int main() {
int arr[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
int size = sizeof(arr) / sizeof(arr[0]);
qsort(arr, size, sizeof(int), compare);
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]); // 1 1 2 3 3 4 5 5 5 6 9
}
return 0;
}
排序结构体数组
根据成员字符排序
struct Stu
{
char name[20];
int age;
};
int cmp_struct_name(const void *e1, const void *e2)
{
return strcmp(((struct Stu *)e1)->name, ((struct Stu *)e2)->name); // lisi、wangwu、zhangsan
}
struct Stu s[] =
{{"zhangsan", 22},
{"lisi", 24},
{"wangwu", 23}};
int struct_size = sizeof(s) / sizeof(s[0]);
qsort(s, struct_size, sizeof(s[0]), cmp_struct_name);
strcmp函数
- strcmp函数用于比较字符串大小 ——> ==0 >0 <0
- strcmp() 函数比较字符串时会逐个字符地比较它们的 ASCII 码值,直到有字符不相同时才停止比较。
- 如果其中一个字符串已经结束了,而另一个字符串还没有结束,那么这个字符串就被认为比另一个字符串更小。
- 头文件<string.h>
根据成员整型排序
struct Stu
{
char name[20];
int age;
};
int cmp_struct_age(const void *e1, const void *e2)
{
/* 从小到大 */
return ((struct Stu *)e1)->age - ((struct Stu *)e2)->age; // 22 23 24
}
struct Stu s[] =
{{"zhangsan", 22},
{"lisi", 24},
{"wangwu", 23}};
int struct_size = sizeof(s) / sizeof(s[0]);
qsort(s, struct_size, sizeof(s[0]), cmp_struct_age);
自定义qsort实现冒泡排序
/* 最终交换字节 */
void Swap(char *buf1, char *buf2, int width)
{
for (int i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void my_qsort(void *base, int sz, int width, int (*cmp)(const void *e1, const void *e2))
{
for (int i = 0; i < sz - 1; i++)
{
for (int j = 0; j < sz - 1 - i; j++)
{
/*
*实参传进去的是待比较的元素的地址
之所以用char类型指针转换,是因为最保险,什么类型指针能访问的字节大小都可以从最小字节1开始活动
比如这里,数据是int类型,那么cmp的实参第一个是 0*4,第二是 1*4,即第一个int数的地址,第二个int数的地址
*/
if (cmp((char *)base + j * width, (char *)base + (j + 1) * width) > 0)
{
Swap((char *)base + j * width, (char *)base + (j + 1) * width, width);
}
}
}
}
my_qsort(arr, sz, sizeof(arr[0]), cmp_int);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]); // 9 8 7 6 5 4 3 2 1 0
}
qsort的实现原理
qsort方法,它使用了一种叫做快速排序(Quicksort)的算法。快速排序是一种分治的排序算法,其基本思想是选择一个基准值,然后将数组中小于基准值的元素放到基准值的左边,大于基准值的元素放到基准值的右边,最终使得基准值左边的所有元素都小于等于基准值,右边的所有元素都大于等于基准值。然后递归地对基准值两边的子数组进行排序,直到整个数组有序为止。
具体步骤
- 选择基准值:从数组中选择一个元素作为基准值(通常选择第一个或者中间的元素)。
- 分区过程:将数组中的元素重新排列,将小于或等于基准值的放在基准值的左边,大于基准值的放在右边。
- 递归排序:对基准值左右两个子数组分别递归执行上述步骤。
快速排序示例代码:
void quicksort(int arr[], int low, int high) {
if (low < high) {
int pivot = partition(arr, low, high);
quicksort(arr, low, pivot - 1);
quicksort(arr, pivot + 1, high);
}
}
int partition(int arr[], int low, int high) {
int pivot = arr[low];
int i = low, j = high;
while (i < j) {
while (i < j && arr[j] >= pivot) {
j--;
}
arr[i] = arr[j];
while (i < j && arr[i] <= pivot) {
i++;
}
arr[j] = arr[i];
}
arr[i] = pivot;
return i;
}
在这个示例中,quicksort 函数进行了递归排序,partition 函数则负责进行分区操作。具体而言,partition 函数以第一个元素作为基准值,然后对数组进行分区操作,并返回新的基准值的位置。对两个子数组进行的递归排序最终能够完成整个数组的排序过程。
快速排序的时间复杂度为 O(n log n),这使得快速排序成为一种非常高效的排序算法。在最好情况下,快速排序的时间复杂度是 O(n log n),而在最坏情况下,时间复杂度为 O(n^2),但平均情况下时间复杂度依然是 O(n log n)。当然,这也取决于选择的基准值和数组的初始状态。
同时,快速排序是一种原地排序算法,它不需要额外的存储空间来存储临时数据,只需要对原始数组进行原地交换和重排列。
总结来说,快速排序通过选择基准值、分区和递归排序等步骤,能够高效地对数组进行排序,在平均情况下的时间复杂度为 O(n log n),适用于大多数场景下的排序需求。因此,qsort 函数使用的快速排序算法在实际应用中具有较高的效率和性能表现。