深度剖析qsort函数(冒泡排序实现qsort部分功能)

目录

介绍

交换数据

普通的交换数据

模拟qsort()的交换数据

冒泡排序

实际操作


介绍

        qsort()函数是一个可以对不同数据类型进行快速排序的函数(上一篇文章有所介绍),用起来十分方便,为了深入理解qsort()函数,所以本文尝试用冒泡排序实现qsort()函数的可以对不同数据类型进行排序的功能。

        首先我们要弄清楚的是,普通的冒泡排序(升序),的核心过程是,如果当前位置的数比他前一个大的话,那么交换这两个数据。对于short ,int,float,long,double等数据类型都是适用的。这里假设,对于char类型的字符串,也可以通过某种方式来判定  当前位置和后一个位置的字符串 谁应该排在前面,然后进行相应的交换两个数据。这里判定的过程不用过于关心(下文会讲明原因),重点是交换两个数据这个过程

交换数据

普通的交换数据

        对于不同数据类型,每个数据所占的内存大小基本上不相同,比如int类型是四个字节,那么交换两个int类型的数据a、b(每个数据占4字节),就是把这a、b分别存在计算机里面的所占四个字节的内容相互交换,如图(十六进制):

         由于int类型的一个数据就是4个字节,所以他交换数据不是一个字节一个字节交换,而是四个字节一起交换。这里表现为,002f1c10和110a4d20是直接全部交换,而不是先00和11交换,再2f和0a交换,再1c和4d交换,再10和20交换

        再比如char类型,char类型一个数据占一个字节,要交换char数据类型的a,b两个变量的值,就是把a和b分别存在计算机里的所占1个字节的内容相互交换,如下(十六进制):

         其他的比如short类型,就是两个字节同时交换,float是四个字节同时交换,double是八个字节同时交换……

模拟qsort()的交换数据

        在这里我们不难发现,在众多数据类型中,我们很轻易就能知道每种数据类型的单个数据所占字节数,且char类型数据所占字节数是最小的,并且正好是1。在这里思考,如果把每一种数据类型都强制类型转换成char类型,然后由sizeof()关键字可以知道每一种数据类型的单个数据所占内存大小,就可以根据所占内存数来一个字节一个字节交换非char类型的数据。比如int 类型的a,b两个数字,强制类型转换成char类型,交换两个数据,然后指针后移一个字节,执行四次,就可以交换两个int类型的数据,代码和运行结果如下:

#include<stdio.h>
int main()
{
	int a = 10, b = 20;
	char* pa = &(char)a;
	char* pb = &(char)b;
	int width = sizeof(int);//int类型的单个数据所占字节数
	for (int i = 0;i < width;i++)
	{
		char temp = *pa;
		*pa = *pb;
		*pb = temp;
		pa++;//指针向后移动一个字节
		pb++;//指针向后移动一个字节
	}
	printf("a=%d,b=%d", a, b);
	return 0;
}

过程如下(这个过程里a和b存储的数据不是10和20,只是为了便于理解): 

        把这个过程封装成函数就是:

        函数传三个参数,分别是要交换的两个数据的指针(被强制类型转换成char之后的),要交换的数据类型的单个数据所占内存数。要交换的各个数据类型所占内存大小width可以用sizeof()关键字算出,比如sizeof(int),sizeof(double),sizeof(struct stu)等等。这样就可以实现用一个函数交换各种数据类型的两个数据。

void swap(char* buf1, char* buf2, int width)
{
	for (int i = 0;i < width;i++)
	{
		char temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}

冒泡排序

        以上就是模拟qsort()排序不同数据类型最核心的部分,接下来就是冒泡排序部分的功能。先上代码。

//尝试用冒泡排序实现类似qsort功能,可以对不同数据类型排序
void Bubble_sort(void *base,int size,int width,int (*cmp)(const void* e1,const void* e2))
{
	for (int i = 0;i < size;i++)
	{
		for (int j = 0;j < size - i - 1;j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

        不难看出,函数内部前半部分和普通冒泡排序别无二致,都是外层循环n次,然后内存循环也一样,不一样的是内层循环里面的算法以及函数传的参数。

        首先从函数传参来看,函数由四个参数,从左到右分别是,待排序的内容的首地址,待排序的元素个数,待排序的元素所占字节数,以及一个自编函数,该自编函数返回值是int 有两个void*类型的参数,该自编函数就和qsort()第四个参数类似(详见上一篇文章)。

        其次看内层循环里面的算法,if里面的条件为真则交换base位置的数据它后面一个数据的内容。

        举个例子,比如base指针是int类型的,那么这个时候width就是4,第一次内层循环时,j=0,base被强制类型转换成了char类型,符合上面swap()函数的要求,然后第二个形参和第一个形参不同的地方就是(j+1),这个时候j+1=1,1*width就是4,这里base指针向后移动了四个位置,由于base被强制类型转换成char类型的指针,所以向后移动一个位置就是向后移动一个字节,所以这里四次一共移动了四个字节,刚好跳过一个int类型所占空间(如下图),然后执行交换4次,所以这里是交换操作的指针虽然是char类型,但是交换的其实是两个int类型的数据。然后从base位置开始,一共有size个数据,每次比较当前数据和后一个数据是否满足交换条件(比如arr[i]>arr[i+1]),满足就交换,最后执行完毕,顺序也排好了。

        在这里,判定两个数据是否该交换的自编函数int (*cmp)(const void* e1,const void* e2)函数和qsort()的第四个函数实际上差不多,也是返回>0,=0,<0三种情况,所以这里不多赘述。

实际操作

        

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct stu
{
	char name[20];
	int age;
};

int str_cmp_by_num(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;//这里由于是整数,相减返回是>0,=0,<0,符合qsort对这里的定义,所以ok
}


int str_cmp_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
	//strcmp返回值正好和快排这个函数要求返回值一样,-1,0,1,所以这里可以直接返回strcmp的结果
}


void swap(char* buf1, char* buf2, int width)
{
	for (int i = 0;i < width;i++)
	{
		char temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}

//尝试用冒泡排序实现类似qsort功能,可以对不同数据类型排序
void Bubble_sort(void *base,int size,int width,int (*cmp)(const void* e1,const void* e2))
{
	for (int i = 0;i < size;i++)
	{
		for (int j = 0;j < size - i - 1;j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}
int main()
{
	int arr[10] = { 2,5,25,67,2,567,2,14,5,67 };
	int sz0 = sizeof(arr) / sizeof(arr[0]);
	Bubble_sort(arr, sz0, sizeof(arr[0]), str_cmp_by_num);

	struct stu  a[4] = { {"zhangsan",38},{"lisi",20},{"wangwu",18},{"niuer",16}};
	int sz = sizeof(a) / sizeof(a[0]);
	Bubble_sort(a,sz,sizeof(a[0]), str_cmp_by_name);

	return 0;
}

        在上面代码排序结构体类型时,值得一提的是,Bubble_sort()第三个参数是sizeof(a[0]),是一个结构体类型数据所占内存大小,因为我们是根据结构体的name数据来作为排序的基础,但是要排序的是一个个结构体类型数据,所以这里是sizeof(a[0]),而不是sizeof(a[0].name)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力努力再努力.xx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值