冒泡排序大家应该到写过吧。但大家可能知道到的冒泡排序有两种方法。而我呢,最近学习到了另外一种方法,现在知道三种方法了。所以想与大家分享一下。但是缺点是第三种是第二种的自实现版。第一种就是我们平常写的普通冒泡排序。第二种就是qsort。第三种就是my_qsort。好那么我们就这三种冒泡排序来讲述。
普通冒泡
大家学习肯定都是先易后难。我们也就先从大家最先学习的冒泡排序开始。当然我不知道,大家对于最常见的冒泡排序是如何理解的,我就先讲解我自己对于冒泡排序的见解。我是这样认为的。因为需要冒泡排序的话,那么就是需要排序后的数组依照升序或者降序来排序,那么我就有双循环语句来写。例如一个数组有10个元素,且下标为0的元素是最大的元素的话。那么我用下标0的元素依次与下一个元素比较,大于的话,用一个零时变量来使这两个值交换。一直到下标为9的时候停止。然后进行下一个循环。当然,因为我已经遍历了一遍确定现在的下标为9的元素是数组中最大的元素所以我们在下一次遍历的时候就可以减少对最后的元素比较。好,那么接下来我们就用代码来更加详细的讲解。
不知道我的代码与大家想的是否有太多的不同之处。或者大家认为这样的代码还有地方可以简洁,大家可以在下方评论区不腻赐教。但其实大家看了上面的代码是否觉得有点啰嗦且繁琐啊。如果我们运气不好的话。这个代码我们要遍历36遍。空间复杂度是否有时候不好满足呀。并且我们如果后面改了,不用整型数组,我们有char类型数组的话。这个代码是不是就不能直接使用了。当然我们可以依照这个模板写一个char类型的冒牌排序,但是大家是否觉得再写一个的话,是否就有点太麻烦了。那是否有这个简单且适应其他类型排序的排序方法嘞。嘿,还真有。在c语言编辑的时候,编辑者就想到了,后面的使用可能会需要对数组进行排序,那么我就写一个库函数吧,后面额人直接使用库函数再添加一些关键数据就可以排序了。这就是我接下里想与大家分享的知识。库函数qsort。
库函数qsort
大家也知道了我们接下来要讲的是库函数qsort。那我们先来了解qsort是什么,由什么构成的
void qsort(
void *base,
size_t nmemb,
size_t size,
int (*compar)(const void *, const void *)
);
头文件:<stdlib.h> qsort()函数的功能是对数组进行排序,数组有nmemb个元素,每个元素大小为size。打大家看上面这个肯定对qsort还是不了解。那么我们直接用代码来实践解决。
当然因为是库函数所以头文件肯定是不能少的,我只是在前面的时候写过了没有照下来,大家在使用的时候记得写出来就可以了。大家看了后,可能会想,不是说可以适用于所有类型吗。你这不是char类型吗?但是大家可以看一下,我在判断大小的时候用的是void*来接收的。为什么用void*来接收嘞。这就不得不说void*的作用了。void大家都知道,无类型,那么无类型的话是不是所有类型的可以接收,相当于一个五边形战士,你来什么对手我都可以打败。但是大家需要注意到,最大的对手自己,所有void*不可以进行就算改变。它只能接收,不能进行改变。这样大家知道我们我在判断大小的时候,返回值的时候要将p1和p2强转为char类型了吧。所有qsort可以排序任意类性数组真相大白了。我们需要在给qsort传递判断大小的时候需要用void*来接收(因为void*可以接收任意类型)。然后返回的时候再强转为数组的类型(使用者肯定知道自己需要排序的数组是什么类型),这样qsort就完美的写出了。当然我可以在写一个int类型的排序,只需要在这个代码上面修改一些部分。
大家可以对照上面的图片,我们写另外一个类型的排序数组,不需要完全重写一个代码,我们只需要将一些关键的类型改变就可以了 。
注:qsort比较大小是使用的ascll码来比较的!!!
自实现qsort
当我们知道qsort如何使用了后,肯定不能止步于此呀,我们要完全将这个函数吃透的话,最好直接写一个代码来实现这个功能。那么接下来我们写的就是my_qsort。大家学习了上面的代码后,就是如果自己写一个的话,需要干什么。我们就以上面的代码来。我们先写,然后总结:
int daxiao(const void *p1,const void *p2)//判断大小
{
return *(int*)p1 - *(int*)p2;
}
void jiaohuan(char*p1, char*p2, size_t haa)//交换值,char类型是为了方便交换,多循环几次就交换全部了
{
for (int a = 0; a < haa; a++)
{
char count = *p1;
*p1 = *p2;
*p2 = count;
p1++;
p2++;
}
}
void my_qsort(void*arr,size_t sz,size_t ha,int (*pf)( void *p1,void *p2))
{
for (int a = 0; a < sz - 1; a++)
{
for (int y = 0; y < sz - 1 - a; y++)
{
if (pf((char*)arr + y *ha, (char*)arr + (y + 1)*ha)>0)//判断,如果大于就交换小于不管
jiaohuan((char*)arr + y*ha, (char*)arr + (y + 1)*ha,ha);
}
}
}
void dayin(int *arr, int sz)//打印结果
{
for (int yy = 0; yy < sz; yy++)
{
printf("%d ", arr[yy]);
}
}
void xixi()
{
int arr[] = { 9, 8, 6, 7, 2, 3, 6, 1, 0, 12 };
int sz = sizeof(arr) / sizeof(arr[0]);
my_qsort(arr, sz, sizeof(arr[0]), daxiao);
dayin(arr, sz);
}
int main()
{
xixi();
return 0;
}
上面是my_qsort的全部代码,那么我们接下来分段来解释每段代码的作用。首先主函数和创建数组传递参数这个大家知道吧。我们先解读一下my_qsort中的数据含义。arr肯定是数组名,sz是数组元素个数,sizeof(arr[0])是数组元素大小,daxiao判断升降序。
我们也都知道qsort一些关键数据需要用void*来接收(因为void*的特性)。但大家应该也注意到了在my_qsort最后接收数据的时候,我们使用的是int (*pf)( void *p1,void *p2)。那这个是什么嘞。首先大家要知道这个叫函数指针,因为我们在这个代码中包含了另外一个需要使用的代码,使用需要将确定其使用的指针名,数据,返回值(当然我们后面会详细的讲解一下这个是什么东西,大家现在可以先记住这个是什么东西,长什么样子)。然后进入代码里面还是经典的双循环。然后判断,那么就是我刚刚说的函数指针判断大小了
那么我们也只是说了,这个代码只是包含的另外一个,那么这个代码是不完全的,所以我们接着就要去晚上这个判断大小的代码。因为函数指针int (*pf)( void *p1,void *p2)中*pf就是这个指针的名字。那么我们接下来( void *p1,void *p2)就是传递的参数,所以大家可以将pf((char*)arr + y *ha, (char*)arr + (y + 1)*ha)理解为子程序名(参数,参数)。那么这里了解了,我们就来完整这个代码:
首先为什么是daxiao这个数组名,是因为在创建数组my_qsort中我们就将判断大小的囊位置确定了名字就叫daxiao所以以防程序错误,我们名字需要一样。然后也是老样子,相减返回,来确定大小。然后回到my_qsort中判断是想要升序还是降序所以交换。
这里大家需要注意的是,为什么我们在接收数据的时候强转char类型。因为char类型只有2个字节。大家应该注意到了吧,我们传递过来的数据中除了交换的两个元素外还有一个字节大小。我们把这两个结合,大家是否想到了。2个字节在c语言数据类型中是最小的,并且使用的类型大小都是2的倍数,那么我们只需要多循环几次,岂不是就可以用2个字节依次交换就交换结束了。然后就是最后的步骤打印了。当然我们在开头就写了打印的代码了。这里就不多赘述了,我们直接看结果。
所以自实现my_qsort只需要以下加点:
1:主函数,创建数组
2:my_qsort接收数据,双循环,判断大小(是否升降序)
3:函数指针实现判断大小
4:交换数据
5:写交换结果
这些就是my_qsort的大概步骤了。当然还有步骤需要大家了解,大家可以多看一下来增加对这个代码的熟悉度。好了如果还有很多不对的地方,希望大家可以在下方评论区写出来。