简析qsort函数

目录

1.简介qsort函数

1.1参数

1.2排序规则

1.3qsort函数使用举例

1.3.1使用qsort函数排列整型数据


1.简介qsort函数

qsort函数是系统自带的一个用于排序库函数。 其底层使用的是快速排序,并且可以排任意类型的数据(比如整型、结构体等)。

这个函数独特之处在与它的参数相对比较特殊。我们进入 cplusplus.com网站搜索 qsort 可以得到如下结果:

1.1参数

先分析最上面的绿字:

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

base 是指针,指向的是待排序的数组的第一个元素;

num是 unsigned int ,指的是待排序数组的元素个数;

size是 unsigned int ,指的是待排序数组的元素大小;

compare 是一个函数指针,指向函数的参数都是 void*(无符号),这样的好处是所排序的数据可以是任何类型,不仅仅局限于 int 类型。

1.2排序规则

在介绍刚刚展示图片中最下面的表格之前,先介绍一下qsort最后一个参数所对应的函数:

int_cmp(const void*p1,const void*p2)
{
	return (*(int*)p1 - *(int*)p2);
}

这个函数的两个参数代表的是比较大小的两个数 p1,p2。但是如果直接写成 return *p1-*p2是错误的,因为 p1,p2是无类型的,不能直接进行加减法。所以这个时候就必须对其强制类型转换成 int*,才能进行加减法。

函数return 的结果会存储在qsort函数最后一个参数中,qsort函数会根据最后一个参数进行排序:(根据表格内容)

返回值qsort函数排序操作
<0将p1放在p2的前面
0保持不变
>0将p1放在p2的后面

1.3qsort函数使用举例

1.3.1使用qsort函数排列整型数据

结合之前介绍的,现在给定一个乱序的整型数组,可以使用qsort函数顺序排列,代码如下:

#include<stdio.h>
#include<string.h>

int_cmp(const void*p1,const void*p2)
{
	return (*(int*)p1 - *(int*)p2);
}

int main()
{
	int arr[] = {5,3,8,9,2,0,6,1,4,7};
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr,sz,sizeof(arr[0]),int_cmp);//这里最后一个参数属于回调函数
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

输出结果:

根据前面分析的表格:我们知道下面这种操作可以使整型数组顺序排列:

int_cmp(const void*p1,const void*p2)
{
	return (*(int*)p1 - *(int*)p2);
}

那如果我们改变一下排序的逻辑:(减法中p1,p2调换顺序),那么qsort顺序排列就变为了逆序排列

int_cmp(const void*p1,const void*p2)
{
	return (*(int*)p2 - *(int*)p1);
}

放到完整代码中输出结果:

1.3.2使用qsort函数排列结构体数据

为了更直观的表示结构体,我们用结构体来表示学生,也就是:

struct stu
{
	char name[20];
	int age;
};

这样来看,结构体中能比较的数据就不止int类型数据,还有char类型数据。

接下来我们来模拟用程序自动给班级上的同学进行排序(可以通过拼音字母排序,也可以通过年龄排序),先创建一个结构体数组来存储下面三位同学的数据:

struct Stu arr[3] = { {"ysy",19},{"xjx",20},{ "hyh",18 } };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),int_cmp);

除了接下来我们要讲的比较函数与整型排序有区别,其余的代码大差不差,如上面的代码。

假设我们现在要按照同学们姓名字母来排序,那我们比较的就是字符串。字符串比较用strcmp函数进行比较。strcmp函数如下图:

我们发现 strcmp函数的Return Value和 qsort函数一致,所以 strcmp函数的计算结果可以直接用 return 输出

int_cmp(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}

注意事项:

1.p1,p2的类型是 void*,代表无具体类型,我们要用强制类型转换告诉程序 p1,p2本质上是结构体指针 struct Stu*(结构体指针) 类型。

2.“->” 是结构体的间接访问,“->”后面接结构体的某种元素。

使用 qsort 函数排序结束后通过传址调用写一个 print 函数将结果打印出来:(完整代码和输出结果如下)

struct Stu
{
	char name[20];
	int age;
};

int_cmp(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}


void print(struct Stu*pa)
{
	for (int i=0;i<3;i++)
	{
		printf("%s %d\n", pa->name, pa->age);
		pa++;
	}
}

int main()
{
	struct Stu arr[3] = { {"ysy",19},{"xjx",20},{ "hyh",18 } };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr,sz,sizeof(arr[0]),int_cmp);
	print(&arr);
	return 0;
}

把比较函数修改成有关年龄的int类型就可以通过年龄大小来对同学们进行排序:

int_cmp(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}

输出结果就变成了下面这种形式(按年龄排序):

2.模拟实现qsort函数

2.1冒泡排序

在我们以前的C语言学习中我们了解过冒泡排序,其底层逻辑是通过数组内元素比较大小并进行交换最终达到排序的目的。

int main()
{
	int arr[10] = { 2,3,6,1,9,4,7,0,8,5 };
	int tmp = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz - 1; i++)
	{
		for (int j=0;j<sz-i-1;j++)
		{
			if (arr[j] > arr[j + 1])
			{
				 tmp=arr[j + 1] ;
				arr[j + 1] = arr[j];
				arr[j] = tmp;
			}
		}
		
	}
	
	for (int r = 0; r < sz; r++)
	{
		printf("%d ",arr[r]);
	}

	return 0;
}

输出结果如下:

2.2改造冒泡排序模拟实现qsort函数排列整型数组

我们刚刚复习了一下冒泡排序,现在要通过改造冒泡排序来模拟实现qsort函数。

2.2.1qsort_model函数(整个函数模拟qsort函数)

1.将qsort函数的参数模拟

void qsort_model(void* base, int num, int size, int (*compare)( void*,void*))
{
	for (int i = 0; i < num-1; i++)
	{
		for (int j = 0; j < num - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				 tmp=arr[j + 1] ;
				arr[j + 1] = arr[j];
				arr[j] = tmp;
			}
		}
	}
}

2.改造qsort_model函数内部

现在传进来的参数void* base是数组arr的首元素的地址,不能直接引用arr[....],所以函数内部所有与arr[...]有关的代码全部要修改。

(1)判断代码的修改

if (arr[j] > arr[j + 1])

这一段代码在冒泡排序中的本意是比较数组相邻两个整型元素的大小关系,但是我们在前文中提到:qsort函数可以交换的数据类型是多样的(int,char,结构体),所以这一性质我们也必须要模拟,判断语句就必须要修改。

我们将目光锁定在 qsort,qsort_model 函数的第四个参数上,这个参数是指向一个有比较作用的函数的指针,所以我们可以把数组内的相邻元素导入比较函数(通过compare指针)。代码如下:

for (int i = 0; i < num-1; i++)
{
	for (int j = 0; j < num - i - 1; j++)
	{
		if (compare(((char*)base + size * j), ((char*)base + size * (j+1)))>0)
		{
			
		}
	}
}

简析 (char*)base + size * j:通过数组的第一个元素的地址传址调用,在compare函数内部起到了比较元素的效果。“+size*j”的作用如下:

一次跳过一个元素的地址,最重要的是:根据元素自身的大小,使得地址每次恰好能跳过数组中一个元素的地址,让比较函数有了多样性(能比较多种类型的数据)

(2)交换代码的修改

交换代码也要写一个专门的swap函数,参数和上面的比较函数的参数一致,再加上控制交换次数的size数据。

swap(((char*)base + size * j), ((char*)base + size * (j + 1)),size);

上面我们用compare函数和swap函数修改了qsort_model函数,接下去要开始写这两个函数内部的代码了。

2.2.2compare函数(比较各种类型的数据)

compare函数可以包括:compare_int,comare_char,compare_struct_Stu_int,compare_struct_Stu_char 这四种类型

int compare_int(const void*p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}

int compare_char(const void* p1, const void* p2)
{
	return strcmp(*(char*)p1, *(char*)p2);
}

int compare_Stu_int(const void* p1, const void* p2)
{
	return(((struct Stu*)p1)->age - ((struct Stu*)p2)->age);
}


int compare_Stu_nane_gae(const void* p1, const void* p2)
{
	return(strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name));
}

介绍qsort函数时已经简介过了,这里不再赘述。

2.2.3swap函数

先上代码:

void swap (char*p1,char*p2,int size)
{
	for (int count = 0; count < size; count++)
	{
		char tmp = 0;
		tmp=*p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++, p2++;
	}
}

swap传的参数和compare部分一致,是两个相邻元素的地址。这里用(char*)的原因是这种类型的地址一次改变一个字节,能有效地满足所有类型数据的比较或者交换的工作

甲乙有x字节要交换(x为未知数),这个时候不方便整体交换,我们就必须创建临时变量。而我们这里的临时变量是1字节的,所以甲乙交换的规则是一个字节一个字节地交换。直到甲乙所有字节都交换结束,甲乙的位置就真正地交换完成了。

交换的次数也就是x,也就是甲乙的大小用qsort_model函数中的size表示。

2.2.4完整代码

模拟qsort函数排列整型数组的完整代码如下:

void swap (char*p1,char*p2,int size)
{
	for (int count = 0; count < size; count++)
	{
		char tmp = 0;
		tmp=*p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++, p2++;
	}
}


int compare_int(const void*p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}



void qsort_model(void* base, int num, int size, int (*compare)( void*,void*))
{
	for (int i = 0; i < num-1; i++)
	{
		for (int j = 0; j < num - i - 1; j++)
		{
			if (compare(((char*)base + size * j), ((char*)base + size * (j+1)))>0)
			{
				swap(((char*)base + size * j), ((char*)base + size * (j + 1)),size);
			}
		}
	}
}

void print(int arr[], int size)
{
	for (int c=0;c<size;c++)
	{
		printf("%d ",arr[c]);
	}
}

int main()
{
	int arr[10] = { 2,3,6,1,9,4,7,0,8,5 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort_model(arr, sz, sizeof(arr[0]), compare_int);
	print(arr, sz);
	return 0;
}

输出结果为:

2.3模拟qsort函数排列其他数据类型

代码框架已经全部完成,要改变排列数据的类型只需要换一个 compare函数以及微调print函数即可,其余的代码基本不用变化(与之前讲qsort函数类似)。上面只举了一个例子,其余数据类型不再赘述。

感谢大家的阅读,如有错误请批评指正。

  • 33
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值