【C语言】qsort 快速排序函数(详解+用法+my_qsort函数模拟实现)

本文详细讲解qsort函数用法,并包含很多知识细节,干活满满!

文章目录

  • qsort函数功能
  • qsort函数声明
    • 函数指针
  • qsort函数用法
    • 整型
    • 浮点型
    • 字符型
    • 字符串型
    • 结构体型
  • my_qsort函数模拟实现

qsort函数功能

排序是一个处理数据常用的功能,qsort(quick sort)快速排序就是八大排序算法之一,时间复杂度O(n)=nlogn。
qsort使用需要包含头文件,让qsort快排函数出彩的不只是它的排序速度,更是它几乎可以排序所有类型数组, 整型、字符型、浮点型,甚至根据结构体某个成员排序,不论升序降序, 都可以轻松实现。
接下来是qsort的用法,以及qsort是如何通过它的参数实现排序。

qsort函数声明

void qsort( void *base, 
			size_t num,
 			size_t width, 
 			int (*cmp )(const void *elem1, const void *elem2 ) );

上面代码是qsort库函数的声明:

  1. 函数是void类型,没有返回值。
  2. base是一个无类型指针,用来接收要被排序的数组首元素地址。void*可以指向任何类型的数据, 从函数参数我们就可以看出,qsort几乎可以排序所有类型。但是对于void*类型指针,我们要注意到一点:

void*类型的指针无法访问地址数据,这是因为指针压根就不知道它要访问多大空间,那么即使能访问得到的数据也毫无意义。因此不能对void*类型指针解引用操作,也不能做地址偏移±操作,这是语法型错误。

  1. num是size_t类型,用来接收数组要被排序的元素个数, 大家可以记住这个类型,**size_t是库用typedef对unsigned int类型命名的新名字,**所以num就是无符号整型。
  2. width也是size_t类型,用来接收数组一个元素的字节大小。
  3. cmp则是一个函数指针,这个函数返回值是int,有两个const修饰的void*型参数, 这个函数是由我们自己编写的,不过非常简单很容易实现。这也是qsort能排序很多类型的原因之一——它很灵活。 有的读者可能不清楚什么是函数指针,下面简要介绍一下。

函数指针

函数是如何被调用的呢,函数也有为它自己在栈区开辟的空间, 理所当然一个函数也有它自身的地址,就连main函数也不例外。
那么有了该函数地址我们就可以调用,或者传递该函数,也有了对应的函数指针来指向函数地址。 函数指针的类型当然是对应函数类型(包括返回类型和参数),下面举个简单例子:

#include 
//我们定义一个简单的返回较大值函数
int max(int a, int b)
{
	return a > b ? a : b;
}

int main()
{
	//函数名和数组名相似都代表地址
	//因为()的优先级高于*,所以要先把*和pf括起来,声明他是一个指针
	int (*pf)(int , int )=max;//也可以写成int(*pf)(int a,int b)=&max;
	//用max直接调用
	int c = max(10, 20);
	printf("max传递10,20后的c:%d\n", c);
	//用函数指针pf调用
	c = pf(10, 30);
	printf("pf传递10,30后的c:%d\n", c);

	return 0;
}

代码输出实例

qsort函数用法

qsort函数声明讲解完了,下面就是如何使用了。废话不多说直接上代码:
注意:cmp函数返回值大于0交换,小于等于0都不交换。

整型

#include 

int cmp(const void*e1,const void*e2)
{
	//因为无类型无法解引用,我们要根据需求强制类型转化,再解引用
	//e1是前一个元素,e2是后一个元素,返回值大于0交换,下面实现的是升序排序
	return *(int*)e1-*(int*)e2;//前比后大交换
	//如果要实现降序,从大到小,就应该写成
	//return *(int*)e2-*(int *)e1;后比前大交换
}

int main()
{
	int arr[10]={2,3,10,9,1,7,5,6,8,4};
	
	//这里我们想把arr升序排序,也就是从大到小排序
	//第一个参数是首元素地址,一般传的都是数组名
	//第二个参数是需要排序元素个数,一般直接填写,或借助sizeof计算
	//第三个参数是一个元素大小,直接用sizeof(arr[0])计算
	//第四个参数是我们编写的比较函数地址,注意此函数返回类型和参数类型是固定的,不能更改。
	qsort(arr,sizeof(arr)/sizeof(arr[0]),sizeof(arr[0]),cmp);
	
	for(int i=0;i<10;i++)
	{
	printf("%d ",arr[i]);
	}
	
	return 0;
}

代码输出实例

浮点型

对于不同类型的排序,主要是我们自己编写的cmp比较函数的修改:

//float类型升序排序
int cmp(const void*e1,const void *e2)
{
	//为了防止差值是零点几几,导致返回值是0,我们这样处理
	if((*(float*)e1-*(float*)e2)>0)
	{
		return 1;
	}
	//其它情况都不交换,我们直接返回0
	return 0;
}

//double类型升序排序
int cmp(const void*e1,const void *e2)
{
	if((*(double*)e1-*(double*)e2)>0)
	{
		return 1;
	}
	return 0;
}

字符型

想必通过上述两种类型举例,你已经可以写出字符型的cmp比较函数了。

int cmp(const void*e1,const void *e2)
{
	//字符类型升序排序
	//字符类型是特殊的整型,ASCII码值就是它的大小,可以直接作差
	return *(char*)e1-*(char*)e2;
}

字符串型

字符串类型比较大小不能直接作差,要借助strcmp函数根据字典序比较大小。

int cmp(const void*e1,const void *e2)
{
	//strcmp两个参数就是字符型指针,直接强制转换传参即可,
	//前大于后会返回值1,前等于后返回值0,前小于后返回值-1。
	return strcmp((char*)e1,(char*)e2);
}

结构体型

结构体排序看似很难,实际上理解清楚qsort用法十分简单。只需要把元素强制转换成结构体类型,再根据结构体某成员排序即可。
我们就拿学生举例,上完整代码:

#include 
#include 
struct student
{
	//我们要分别根据学生的名字升序,分数降序排序
	char name[20];
	int score;
}//名字升序排序函数
int name_cmp(const void *e1, const void *e2)
{
	//把元素强制转换成结构体类型,再根据结构体某成员排序
	return strcmp(((student *)e1)->name, ((student *)e2)->name);
}

//分数降序排序函数
int score_cmp(const void *e1, const void *e2)
{
	return ((student*)e2)->score-((student*)e1)->score;
}

int main()
{
	struct student arr[3]={{"zhangsan",80},{"lisi",92},{"wangwu",99}};
	
	//根据名字升序排序
	qsort(arr,sizeof(arr)/sizeof(arr[0]),sizeof(arr[0]),name_cmp);
	
	printf("名字升序排序后:\n");
	for(int i=0;i<3;i++)
	{
		printf("%s %d\n",arr[i].name,arr[i].score);
	}
	printf("\n");
	
	//根据分数降序排序
	qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), score_cmp);
	
	printf("分数降序排序后:\n");
	for(int i=0;i<3;i++)
	{
		printf("%s %d\n",arr[i].name,arr[i].score);
	}
	
	return 0;
}

【C语言】qsort 快速排序函数(详解+用法+my_qsort函数模拟实现)_第1张图片

my_qsort函数模拟实现

学习完上面,你已经势不可挡了,大部分类型数组排序都可以轻松搞定。
下面我们更深入一步,用冒泡排序的方式,观察这些参数是如何巧妙地完成排序的:

void swap(char *str1, char *str2, int width)
{
	char tmp = 0;
	//对应交换一个元素大小width字节内容
	for (int i = 0; i < width; i++)
	{
		tmp = *(str1 + i);
		*(str1 + i) = *(str2 + i);
		*(str2 + i) = tmp;
	}
}

void my_qsort(void *arr, int num, int width, int (*cmp)(const void *, const void *))
{
	//冒泡排序框架
	for (int i = 0; i < num - 1; i++)
	{
		for (int j = 0; j < num - i - 1; j++)
		{
			//我们想比较两个元素大小,只需要把每个元素的首字节地址传递过去即可
			//cmp会帮我们实现类型转换比较,注意每个地址的偏移量不要写错
			if (cmp((char *)arr + j * width, (char *)arr + (j + 1) * width) > 0)
			{
				//满足交换条件了,那么只需要将这两个width大的空间内容对应交换
				//我们把交换独立封装为函数
				swap((char *)arr + j * width, (char *)arr + (j + 1) * width, width);
			}
		}
	}
}

int int_cmp(const void *e1, const void *e2)
{
	return *(int *)e1 - *(int *)e2;
}

int char_cmp(const void *e1, const void *e2)
{
	return *(char *)e1 - *(char *)e2;
}

int main()
{
	int darr[10] = { 1,2,4,6,9,10,3,8,7,5 };
	char carr[10+1] = "ahkgdefzpx";

	my_qsort(darr, 10, sizeof(int), int_cmp);
	my_qsort(carr, 10, sizeof(char), char_cmp);

	for (int i = 0; i < 10; i++)
	{
		printf("%d ",darr[i]);
	}
	printf("\n");

	printf("%s", carr);

	return 0;
}

代码输出实例


如果你能理解,恭喜你真的学会了个很NB的东西!
码字不容易,欢迎关注、点赞、收藏、评论、转发。

你可能感兴趣的:(C语言库函数篇,c语言,数据结构,算法,c++)