C语言:深入浅出qsort方法,编写自己的qsort完成冒泡排序

目录

什么是qsort? 

函数原型

比较函数 compar

排序整型数组

排序结构体数组

根据成员字符排序

 strcmp函数

根据成员整型排序

自定义qsort实现冒泡排序 

qsort的实现原理 

具体步骤

快速排序示例代码: 


什么是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)的算法。快速排序是一种分治的排序算法,其基本思想是选择一个基准值,然后将数组中小于基准值的元素放到基准值的左边,大于基准值的元素放到基准值的右边,最终使得基准值左边的所有元素都小于等于基准值,右边的所有元素都大于等于基准值。然后递归地对基准值两边的子数组进行排序,直到整个数组有序为止。

具体步骤

  1. 选择基准值:从数组中选择一个元素作为基准值(通常选择第一个或者中间的元素)。
  2. 分区过程:将数组中的元素重新排列,将小于或等于基准值的放在基准值的左边,大于基准值的放在右边。
  3. 递归排序:对基准值左右两个子数组分别递归执行上述步骤。

快速排序示例代码: 

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 函数使用的快速排序算法在实际应用中具有较高的效率和性能表现。

猜你喜欢

转载自blog.csdn.net/dabaooooq/article/details/134293295