c语言库函数qsort模拟实现

1. qsort介绍

官方文档:qsort @cplusplus

void qsort (void* base, size_t num, size_t size,
            int (*compar)(const void*,const void*));

qsort一共四个参数:

  • void* base: 要排序数组的起始地址,一般为数组名,也可以为任意有效地址 (比如arr, arr + 1, …)
  • size_t num: 要排序的数据长度,一般为数组长度,也可以为任意可访问的无符号整形(比如len, len - 1, …)
  • size_t size: 要排序的数组中单个元素所占字节数,一般为sizeof(*arr),其中*arr为数组首元素
  • int (*compar)(const void*,const void*): 数组中元素的比较规则,函数指针类型,参数为两个void*类型的指针,返回值为int类型。用于传入一个自定义比较方式的函数地址

int compar(void* a, void* b)的返回值有特定的含义
先将void强转为指定自定义

// 以整型数据为例
int* pa = (int*)a;
int* pb = (int*)b;
  • 当返回值大于0时,在a, b强转为自定义类型的指针后,返回前者元素自定义“大于”后者元素
  • 当返回值小于0时,在a, b强转为自定义类型的指针后,返回前者元素自定义“小于”后者元素
  • 当返回值等于0时,在a, b强转为自定义类型的指针后,返回前者元素自定义“等于”后者元素
    返回值

2. 实现用于测试的比较规则函数

比较整形函数

int cmp_int(const void* a, const void* b) {
	// 将a,强转为int*,使a解引用后可以访问四个字节
	// 由于解引用后为整数可以直接比较
	// 注意将a, b对调可使排序逆序
	return *(int*)a - *(int*)b;
}

比较自定义类型
先定义一个自定义类型数组,以学生Stu为例:

typedef struct Stu {
	char name[30];
	int age;
}Stu;

再创建一个Stu类型的数组

Stu s[] = {
	{"zhangsan", 19},
	{"wangwu", 18},
	{"lisi", 18},
	{"zhaoliu", 20},
	{"qianqi", 17}
};

实现打印Stu类型数组:

void print_Stu(Stu* s, int nums) {
	printf("[\n");
	for (int i = 0; i < nums; ++i) {
		printf(i == nums - 1 ? "  {%s, %d}\n" : "  {%s, %d},\n", s[i].name, s[i].age);
	}
	printf("]\n");
}

main函数调用测试打印:

void test1() {
	Stu s[] = {
		{"zhangsan", 19},
		{"wangwu", 18},
		{"lisi", 18},
		{"zhaoliu", 20},
		{"qianqi", 17}
	};
	int nums = sizeof(s) / sizeof(*s);
	print_Stu(s, nums);
}

打印Stu数组
实现Stu类型的比较规则

先比较年龄,由小到大,年龄相同按字典序(ASCII)比较姓名

为例

void cmp_Stu(const void* s1, const void* s2) {
	// 将s1, s2强转为指向Stu类型的指针
	Stu* p1 = (Stu*)s1;
	Stu* p2 = (Stu*)s2;
	// 年龄相同吗,相同按字典序(ASCII)比较姓名
	//            不相同直接返回年龄差
	// 注意将p1, p2对调可使排序逆序
	return p1->age == p2->age ? strcmp(p1->name, p2->name) : p1->age - p2->age;
}

先使用库函数qsort()调用该排序规则

void test1() {
	Stu s[] = {
		{"zhangsan", 19},
		{"wangwu", 18},
		{"lisi", 18},
		{"zhaoliu", 20},
		{"qianqi", 17}
	};
	int nums = sizeof(s) / sizeof(*s);
	printf("before sort: \n");
	print_Stu(s, nums);
	qsort(s, nums, sizeof(*s), cmp_Stu);
	printf("after sort: \n");
	print_Stu(s, nums);
}

正确输出

3. 实现快速排序

快速排序讲解 @蓝不过嗨呀 @billbill ps: 不是我

参考挖坑法实现的快速排序算法(Java, int类型数组的排序)

// 快速排序函数组
public static void quickSort(int[] arr) {
		// 封装一层减少传参
        quickSort(arr, 0, arr.length - 1);
    }

    private static void quickSort(int[] arr, int left, int right) {
    	// 当递归到left不再小于right时停止递归
        if (left >= right) {
            return;
        }
        // 获得 基准元素 的索引
        // 注意与partition()内的base区分
        int base = partition(arr, left ,right);
        quickSort(arr, left, base - 1);
        quickSort(arr, base + 1, right);
    }

    private static int partition(int[] arr, int left, int right) {
    	// 以左值为 基准元素 保存一份(为元素值)
    	// 方便后续覆盖基准元素
        int base = arr[left];
        while (left < right) {
            while (left < right && arr[right] >= base) {
                right--;
            }
            arr[left] = arr[right];
            while (left < right && arr[left] <= base) {
                left++;
            }
            arr[right] = arr[left];
        }
        arr[left] = base;
        return left;
    }

改为c语言版的任意类型排序,即my_qsort()

因为多次涉及到单字节拷贝(c语言中char类型占一个字节,故传入char*类型指针),所以将其封装为一个函数copy_char
参数解释:

  • char* dest: 目标起始地址
  • const char* const src: 源起始地址,const防止修改源指针指向及该地址数据
  • len: 拷贝的长度,在本文章可以看作,要排序数组内单个元素的所占字节数

该函数功能相当于同一类型的元素赋给左值

void copy_char(char* dest, const char* const src, size_t len) {
	for (int i = 0; i < len; ++i) {
		// 一个字节一个字节拷贝
		*(dest + i) = *(src + i);
	}
}

4. 外层的my_qsort()

暴露在外的接口与库函数qsort()一致

void my_qsort(void* arr, size_t len, size_t size, int (*compare_rule)(const void*, const void*)) {
	// size即为单个要排序元素的大小
	// 调用快排函数
	quick_sort(arr, size, 0, len - 1, compare_rule);
}

快排函数的c语言实现

参数解释:

  • 其中left,与right均为数组元素的索引
  • compare_rule: 用于转入上文所实现的比较规则函数
void quick_sort(void* arr, size_t size, int left, int right, int (*compare_rule)(const void*, const void*)) {
	if (left >= right) {
		return;
	}
	// 调用partition()函数获得基准元素并将,基准元素放置到正确位置
	// 返回基准索引
	int base_index = partition(arr, size, left, right, compare_rule);
	// 递归分别求基准元素左右的元素们,即将数组一分为二,并在每次递归时,
	// 再次分别一分为二,进一步缩小未排序的范围
	quick_sort(arr, size, left, base_index - 1, compare_rule);
	quick_sort(arr, size, base_index + 1, right, compare_rule);
}

5. 内层的分区函数

  • 返回值: 新的左值索引,作为新的基准值索引
int partition(void* arr, size_t size, int left, int right, int (*compare_rule)(const void*, const void*)) {
	// 基准值的起始地址
	char* base = (char*)arr + left * size;
	// 使用动态内存分配保存一个要排序元素大小的内存
	char* pivot = (char*)malloc(size);
	// 储存基准元素
	copy_char(pivot, base, size);
	// 将void*指针强转为长度为一个字节的char*类型
	char* a = (char*)arr + left * size;
	char* b = (char*)arr + right * size;
	while (left < right) {
		// 当右边界值大于基准值时,右边界及a一直左移
		while (left < right && compare_rule(b, pivot) >= 0) {
			// right先自减,让b指向新的右边界地址
			// 相当于(MyType*)b指向数组上一个元素
			b = (char*)arr + --right * size;
		}
		// 当不满足上述条件时,将arr[right]赋值给arr[left](即坑位)
		// 并在此地留下坑位
		copy_char(a, b, size);
		// 当左边界值小于基准值时,左边界及b一直右移
		while (left < right && compare_rule(a, pivot) <= 0) {
			// left先自增,让a指向新的左边界地址
			// 相当于(MyType*)a指向数组下一个元素
			a = (char*)arr + ++left * size;
		}
		// 当不满足上述条件时,将arr[left]赋值给arr[right](即坑位)
		// 并在此地留下坑位
		copy_char(b, a, size);
	}
	// 循环结束将基准值放到正确索引,即最后更新的坑位
	copy_char(a, pivot, size);
	// pivot使用完了以后及时释放内存
	free(pivot);
	return left;
}

至此my_qsort()实现完毕

6. 测试函数组

int类型测试

void print_arr_int(int* arr, int len) {
	printf("[");
	for (int i = 0; i < len; ++i) {
		printf(i == len - 1 ? "%d" : "%d, ", arr[i]);
	}
	printf("]\n");
}

int cmp_int(const void* a, const void* b) {
	return *(int*)a - *(int*)b;
}

void test0() {
	int arr[] = {
		1, 4, 3, 3, 2, 2, 3, 6, 8, 4
	};
	int len = sizeof(arr) / sizeof(*arr);
	printf("before sort: \n");
	print_Stu(s, nums);
	my_qsort(s, nums, sizeof(*s), cmp_Stu);
	printf("after sort: \n");
	print_Stu(s, nums);
}

int main() {
	test0();
	return 0;
}

测试结果:
整形数组排序
自定义类型Stu数组测试

// 比较规则
void cmp_Stu(const void* s1, const void* s2) {
	Stu* p1 = (Stu*)s1;
	Stu* p2 = (Stu*)s2;
	return p1->age == p2->age ? strcmp(p1->name, p2->name) : p1->age - p2->age;
}
// 打印Stu数组内容
void print_Stu(Stu* s, int nums) {
	printf("[\n");
	for (int i = 0; i < nums; ++i) {
		printf(i == nums - 1 ? "  {%s, %d}\n" : "  {%s, %d},\n", s[i].name, s[i].age);
	}
	printf("]\n");
}
// 测试函数
void test1() {
	Stu s[] = {
		{"zhangsan", 19},
		{"wangwu", 18},
		{"lisi", 18},
		{"zhaoliu", 20},
		{"qianqi", 17}
	};
	int nums = sizeof(s) / sizeof(*s);
	printf("before sort: \n");
	print_Stu(s, nums);
	my_qsort(s, nums, sizeof(*s), cmp_Stu);
	printf("after sort: \n");
	print_Stu(s, nums);
}

int main() {
	test1();
	return 0;
}

测试输出:
Stu数组排序
到此qsort()以全部模拟实现完毕

源码

源码
END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值