深入解析C语言qsort排序原理

编程达人挑战赛·第6期 10w+人浏览 123人参与

深入解析C语言qsort排序原理

大家好,这篇文章将带你深度理解 C 语言 qsort 排序的底层原理,从内存布局、比较函数调用,到元素交换和排序算法逻辑,逐步解析,让初学者也能看懂。

如果你觉得文章对你有帮助,欢迎 点赞支持 ,我会持续更新更多 算法与数据结构相关内容。

同时,如果你喜欢我的内容,可以 关注我/订阅专栏,不错过后续更多实用教程和深度解析。
qsort 是 C 标准库 <stdlib.h> 提供的通用排序函数,它可以对任意类型的数组进行排序。

函数原型:

void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*));
参数含义
base指向数组首元素的指针,类型是 void*,所以可以是任意类型数组
nitems数组中元素的数量
size每个元素的字节大小(sizeof(元素类型)
compar比较函数指针,接受两个 const void* 参数,返回值为整数,规定如下:<0 表示第一个元素小于第二个0 表示相等>0 表示第一个元素大于第二个

compar 模板

整数升序排序

int cmp_int(const void *a, const void *b) {
    // 先转换类型
    int x = *(int*)a;
    int y = *(int*)b;
    
    // if (x < y) {
    //     return -1;
    // }
    // else if (x > y) {
    //     return 1;
    // }
    // 
    // return 0;
    
    return x - y; // 升序排序
    
    // 如果要降序呢?调换一下位置即可
    // return y - x;
    
}

通过解析 cmp_int 理解 compar

我们要做的是:qsort 排序整数数组。

函数原型:

int cmp_int(const void *a, const void *b)

Ⅰ为什么参数是 const void*

  1. qsort 是一个 通用排序函数,可以排序任何类型的数组:整数、浮点数、结构体等等。
  2. void* 表示 指向任意类型的指针
  3. const 表示 函数不会修改数组里的值,只是比较它们大小。

也就是说,ab指向数组元素的地址,但函数不会改变它们的内容。

Ⅱ先把指针变成我们能用的整数

int x = *(int*)a;
int y = *(int*)b;

逐步解释:

  1. (int*)a → 把 void* 转成 int*,告诉编译器这是一个整数地址
  2. *(int*)a → 取出这个地址上的整数,也就是数组里的真实数字
  3. 同理,y = *(int*)b; 也取出第二个数字

Ⅲ比较两数

方式一:原型形式

if (x < y) return -1;  // x 小于 y → 排在前面
else if (x > y) return 1; // x 大于 y → 排在后面
else return 0;           // x 等于 y → 不动

解释:

  • qsort 通过返回值判断两个元素的顺序
  • <0 → a 在前
  • 0 → 不变
  • >0 → b 在前

方式二:简化写法(可能溢出)

return x - y;
  • 直接用整数相减得到结果
  • 简洁,但如果数字很大或很小,可能溢出(计算结果超出整数范围),排序就错了

方式三:优化(推荐)

return (x > y) - (x < y);
  • (x > y) → 如果 x > y 返回 1,否则 0
  • (x < y) → 如果 x < y 返回 1,否则 0
  • 减法得到 -1、0、1 → 完美对应 qsort 规则
  • 没有溢出风险,代码既优雅又安全

Ⅳ示例

#include <stdio.h>
#include <stdlib.h>

int cmp_int(const void *a, const void *b) {
    int x = *(int*)a;
    int y = *(int*)b;
    return (x > y) - (x < y); 
}

int main() {
    int arr[] = {5, 2, 9, 1};
    int n = sizeof(arr)/sizeof(arr[0]);

    qsort(arr, n, sizeof(int), cmp_int);

    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    return 0;
}

输出:

1 2 5 9

浮点数降序排序

int cmp_double_desc(const void *a, const void *b) {
    // 先转换类型
    double x = *(double*)a;
    double y = *(double*)b;
    
    return (y > x) - (y < x); // 返回正数/负数/0
}

提问:浮点数排序可以使用 return x - y 吗?

答案是不可以

int cmp_float(const void *a, const void *b) {
    double x = *(const double*)a;
    double y = *(const double*)b;
    return x - y;  // 错误
}

原因

  1. 返回值类型错误
    • qsort 比较函数必须返回 int
    • x - ydouble,会被强制转换为 int
    • 小差值(如 0.0001)转换成 int 会变成 0 → 排序错误
  2. 浮点精度问题
    • 浮点数很接近,减法可能丢失精度
    • 导致相近的两个元素被认为相等 → 排序结果不正确

字符串数组排序

int cmp_str(const void *a, const void *b) {
    // 先转换类型
    const char *str1 = *(const char **)a;
    const char *str2 = *(const char **)b;

    return strcmp(str1, str2);
}

结构体排序

typedef struct {
    int a;
    int b;
} Item;

int cmp_a_b(const void *x, const void *y) {
    // 先转换类型
    const Item *i1 = (const Item*)x;
    const Item *i2 = (const Item*)y;
    
    // 常用
    if (i1->a != i2->a)
        return i1->a - i2->a;   // a 升序
    
    return i1->b - i2->b;       // a 相同则 b 升序
    
    // 防溢出优化
    // if (i1->a != i2->a)
    //     return (i1->a > i2->a) - (i1->a < i2->a);  // a 升序
	//
    // return (i1->b > i2->b) - (i1->b < i2->b);	   // a 相同则 b 升序
}

内部排序原理

假设我们要排序一个结构体数组:

typedef struct {
    int a;
    int b;
} Item;

调用方式:

qsort(arr, n, sizeof(Item), cmp_a_b);

内部算法选择

C 标准库 qsort 的具体实现因平台而异,但通常采用 混合排序算法

  1. 快速排序 (Quicksort)
    • 平均时间复杂度 O(n log n)
    • 通过基准分区,把小的放左,大的放右
    • 对递归深度过大或接近有序数组可能退化
  2. Introsort / 混合排序
    • 先用快速排序
    • 当递归深度过深 → 切换为堆排序,保证最坏 O(n log n)
    • 小数组使用插入排序,减少递归开销

比较函数调用机制

  • qsort 通过传入指针调用比较函数:
cmp_a_b(&arr[i], &arr[j]);
  • 内部操作步骤:
  1. void*Item*
  2. 取出需要比较的字段(例如 a, b
  3. 返回 -1/0/1 表示排序顺序

注意:比较函数只读指针,不修改数组,只提供顺序信息。

元素交换机制

  • 当比较结果显示顺序需要调整时,qsort 交换元素:
swap(void *p1, void *p2, size_t size) {
    char tmp[size];
    memcpy(tmp, p1, size); // 临时保存 p1
    memcpy(p1, p2, size);  // p1 = p2
    memcpy(p2, tmp, size); // p2 = p1
}
  • 对结构体数组:
    • 每次交换整个结构体内存块(包括所有字段)

排序流程(以快速排序为例)

  1. 选择基准元素 (pivot)
    • 例如选择第一个或中间元素
  2. 分区
    • 遍历数组,把小于 pivot 的元素放左边,大于 pivot 的放右边
  3. 递归
    • 分别对左半部分和右半部分递归排序
  4. 优化
    • 小数组 → 插入排序
    • 递归过深 → 堆排序
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值