当前位置:   article > 正文

C语言,冒泡排序(含qsort函数的使用与实现(回调函数))_c语言 函数中使用嵌套qsort函数

c语言 函数中使用嵌套qsort函数

目录

1 . 基础版的冒泡排序

2 . 用了qsort的冒泡排序(使用qsort函数)

3 . 冒泡排序之 qsort 函数的实现


1 . 基础版的冒泡排序

我们先来看一下代码

  1. void bubble_sort(int* arr, int sz)
  2. {
  3. int i = 0;
  4. int j = 0;
  5. for (i = 0; i < sz - 1; i++)
  6. {
  7. for (j = 0; j < sz - 1 - i; j++)
  8. {
  9. if(arr[j] > arr[j + 1])
  10. {
  11. int tmp = 0;
  12. tmp = arr[j];
  13. arr[j] = arr[j + 1];
  14. arr[j + 1] = tmp;
  15. }
  16. }
  17. }
  18. }
  19. int main()
  20. {
  21. int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
  22. int sz = sizeof(arr) / sizeof(arr[0]);
  23. bubble_sort(arr, sz);
  24. int i = 0;
  25. for (i = 0; i < 10; i++)
  26. {
  27. printf("%d ", arr[i]);
  28. }
  29. return 0;
  30. }

我们如果要排序的话,就需要给出一组数吧,那么我们先 int arr[ ] = { } 定义出一组数,我们定义出来之后,是不是就该就去实现冒泡函数啦。

那么我们先将该函数命名为bubble_sort吧,既然要调用函数,那我们是不是应该将函数的地址(也就是函数名)传过去,再在bubble_sort函数中用一个指针来接收啊,同时我们再将数组的元素个数传过去(此处用sz来命名元素个数),用int sz接收

而在使用完bubble_sort函数之后我们就用一个for循环将排序后的arr数组打印一下

如下:

  1. void bubble_sort( int* arr ,int sz)
  2. {
  3. }
  4. int main()
  5. {
  6. int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
  7. int sz = sizeof(arr)/sizeof(arr[0]);
  8. bubble_sort(arr, sz);
  9. int i = 0;
  10. for (i = 0; i < 10; i++)
  11. {
  12. printf("%d ", arr[i]);
  13. }
  14. return 0;
  15. }

接下来我们来重点看看bubble_sort函数的实现

首先,冒泡排序是怎么个冒泡法呢?这样来看:假设我现在要让一组数从小到大进行排列,而我给出的是

9,8,7,6,5,4,3,2,1

而冒泡完一趟之后,就变成了:

8,7,6,5,4,3,2,1,9

接着进行第二趟冒泡:

7,6,5,4,3,2,1,8,9

......

最后就变成了

1,2,3,4,5,6,7,8,9

他的机制是这样的:   先让第一个数和第二个数进行比较,如果第一个数字比第二个数字大,就将其进行交换。交换完之后再让第二个数字与第三个数字进行比较,以此类推......

每一趟大概是这样的:

9,8,7,6,5,4,3,2,1  -> 

8,9,7,6,5,4,3,2,1  ->

8,7,9,6,5,4,3,2,1  ->

......

8,7,6,5,4,3,2,1,9

所以当我们知道冒泡是这么一个东西之后,我们就想:一趟冒泡是这样的,那如果我要将这一整组数字排完序,那不就需要9-1趟吗?(注:9是数组的元素个数)那我们能否用先用一个for循环表示一共有多少趟呢?然后再在这个for循环里面加一个for循环表示每一趟for循环?答案是肯定的。

但是问题来了:我们第一层for循环知道限制条件是  "个数 - 1",也就是sz - 1,那我们的第二层呢?每一趟冒泡排序之后就有一个数不用再被排序,那我们就将这个条件设置为 (sz - 1) - i,你想啊,i 代表的是每一趟,到了第几趟 i 就等于几,也就相当于有多少个数不用再被排序

代码如下:

  1. void bubble_sort(int* arr, int sz)
  2. {
  3. int i = 0;
  4. int j = 0;
  5. //第一层for循环表示有多少趟冒泡
  6. for (i = 0; i < sz - 1; i++)
  7. {
  8. //第二层for循环表示每一趟冒泡
  9. for (j = 0; j < sz - 1 - i; j++)
  10. {
  11. //语句
  12. }
  13. }
  14. }

如果没法理解for循环嵌套的话,那我们这么看:我现在打印一个3*5的数字矩阵,每一行都是1,2,3,4,5。如下:

  1. int main()
  2. {
  3. int i = 0;
  4. int j = 0;;
  5. //第一层每一次循环代表一行
  6. //当i=1时代表第一行,i=2时代表第二行...
  7. for (i = 1; i <= 3; i++)
  8. {
  9. //第二层代表每一行的每个数字
  10. //i=1时,j=1代表第一行第一个数,j=2代表第一行第二个数...
  11. for (j = 1; j <= 5; j++)
  12. {
  13. printf("%d ", j);//打印结果
  14. }
  15. printf("\n");//每打印一行之后就用\n换行
  16. }
  17. return 0;
  18. }

结果如下图所示 

那我们讲完for循环的嵌套之后就继续来看一看bubble_sort的实现

这时候我们已经用for循环的嵌套表示出了每一趟冒泡,那我们想一想:我每一趟冒泡是不是都需要将两个数字进行对比啊,而现在要对比的两个数就是 arr[ i ] 和 arr[ i + 1 ]。那我们就先定义出一个tmp变量,将arr[ i ] 放进去,再将 arr[ i + 1 ] 放进 arr[ i ] 里,最后把 tmp 放进 arr[ i + 1 ] 里就实现了交换这个过程啦!!代码如下:

  1. void bubble_sort(int* arr, int sz)
  2. {
  3. int i = 0;
  4. int j = 0;
  5. for (i = 0; i < sz - 1; i++)
  6. {
  7. for (j = 0; j < sz - 1 - i; j++)
  8. {
  9. if (arr[j] > arr[j + 1])
  10. {
  11. int tmp = 0;
  12. tmp = arr[j];
  13. arr[j] = arr[j + 1];
  14. arr[j + 1] = tmp;
  15. }
  16. }
  17. }
  18. }

2 . 用了qsort的冒泡排序(使用qsort函数)

在学完了基础版的冒泡排序之后,相信你对冒泡排序也算是有了一个基本的概念了。但是仅仅是用分支与循环去实现冒泡的话,我们会发现这个冒泡有很大的局限性:如果我此时定义的不是一个整型数组,而是一个浮点型的数组呢?如果是一串结构体呢?你会发现这排不了,仅用分支与循环的内容的话只能给整形排序。

那我们这里介绍一个库函数叫 qsort ,需要引的头文件是#include<stdlib.h>,q 代表的是quick,sort是排序,所以qsort代表的就是快速排序。使用qsort就能实现其他的排列方式

qsort(因为代码比较长,所以换了一行)

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

注:如图内容可cplusplus.com - The C++ Resources Network 上找到

我们简单解释一下这里面的内容:qsort函数在调用时需要传四个参数

假设我们定义的数组是float arr[ ] = { 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0 }

  1. int cmp_float(const void* e1, const void* e2)
  2. {
  3. return ((int)(*(float*)e2 - *(float*)e1));
  4. }

1 . base    Pointer to the first object of the array to be sorted, converted to a void*(指向要排序的数组中第一个对象的指针,转换为 void*),因为我们假定的数组是浮点型的,那么我们的第一个参数就是    arr

2 . num     Number of elements in the array pointed to by base.size_t is an unsigned integral type . 简单点理解:第二个参数就是数组的元素个数。根据上文,我们可以用 int sz = sizeof (arr) / sizeof(arr[ 0 ]) 来进行实现

3 . size      Size in bytes of each element in the array.size_t is an unsigned integral type. 简单点理解:第三个参数就是该数组每个元素所占的字节的大小,这里我们传 sizeof( arr[ 0 ] ) 就行了

4 . compar Pointer to a function that compares two elements.This function is called repeatedly by qsort to compare two elements. It shall follow the following prototype:  ( 指向比较两个元素的函数的指针。qsort 反复调用此函数以比较两个元素 )简单点理解:既然qsort函数可以将不同类型的数组进行排序,而不同类型之间的比较方法又不一样,那就需要我们将比较的方法写成一个函数,再将这个函数的地址传进来就可以啦。这里我们传如上所示代码里的函数的地址:cmp_float

注:(*cmp_float)和cmp_float是一个东西

那我们在了解了之后,我们就能得到如下代码:

  1. int cmp_float(const void* e1, const void* e2)
  2. {
  3. return ((int)(*(float*)e2 - *(float*)e1));
  4. }
  5. void test()
  6. {
  7. float f[] = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 };
  8. int sz = sizeof(f) / sizeof(f[0]);
  9. qsort(f, sz, sizeof(f[0]), cmp_float);
  10. int i = 0;
  11. for (i = 0; i < sz; i++)
  12. {
  13. printf("%.1f ", f[i]);
  14. }
  15. }
  16. int main()
  17. {
  18. test();
  19. return 0;
  20. }

我们来理一理逻辑:在test函数内部先是创建了一组待排序的浮点数,计算出了数组的元素个数sz,然后通过qsort函数进行排序,最后用for循环将该数组内的数全部打印出来。

接下来我们来重点说说这第四个参数:

  1. int cmp_float(const void* e1, const void* e2)
  2. {
  3. //语句
  4. }
  5. //假设我们定义的是这么一个数组
  6. float f[] = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 };

想象一下,我们进入了qsort函数内部,我们是不是应该将两个元素进行比较啊,比如我们要比较1.0和2.0,那我们就是将这两个元素的地址传到cmp_float函数内部去,再通过浮点型的比较方式进行比较

但有人会问:我见过int*,char*等等,这个void*是什么呀?你可以这么去理解,

  1. int main()
  2. {
  3. int a = 1;
  4. int* p = &a;
  5. char* pc = &a;
  6. char* ph = (char*)&a;
  7. void* ps = &a;
  8. return 0;
  9. }

你看,当我们这么写的时候,a 是整形,如果要将地址放到char* 里的话就需要强制类型转换,但放在void* 里就不需要。你可以这么说,void* 就是一个能放所有地址的东西。

但注意,void* 不能+-,也不能解引用

接下来有人就看到说,为什么这个函数的返回类型是int 呀?是因为qsort函数规定说如果a<b,就返回一个小于0的整数,如果a=b,就返回0,a>b,就返回一个大于0的整数,如下图

那好,接下来我们就来实现一下cmp_float函数,

cmp_float 将两个元素的地址用void* 接收,并分别命名为a  b,既然是void*,我们就将其强制类型转换成float* ,又因为是地址,所以我们需要将其解引用一下,就得到了  * ( flaot* ) a,同理,我们也可以得到  * ( flaot* ) b,又因为要返回一个整数,那我们就将两个相减之后的值强制类型转换为int再 return 回去不就可以啦!代码如下:

  1. int cmp_float(const void* e1, const void* e2)
  2. {
  3. return ((int)(*(float*)e1 - *(float*)e2));
  4. }

同理,如果是结构体变量的话,我们也可以将 a 转换成相应的类型,这里假设是stu*,那么我们将其强制类型转换了之后就可以用  ->  来直接选择结构体内想比较的来进行比较。注意:我们将其强制转换成这个类型的指针之后,这个指针它就指向的是这个结构体的类型,它是可以直接访问结构体成员的,所以我们这里不需要解引用。例子如下:

  1. int cmp_stu_by_age(const void*a, const void*b)
  2. {
  3. return (int)(((stu*)e1)->age - ((stu*)e2)->age);
  4. }

讲到这里,相信大家已经清楚qsort函数的使用方法了,那我们接下来来进行下一步:qsort函数的实现!!

3 . 冒泡排序之 qsort 函数的实现

先上代码!!

  1. int compare(const void* e1, const void* e2)
  2. {
  3. return *(int*)e1 - *(int*)e2;
  4. }
  5. void swap(char* buf1, char* buf2, int width)
  6. {
  7. int i = 0;
  8. for (i = 0; i < width; i++)
  9. {
  10. char tmp = *buf1;
  11. *buf1 = *buf2;
  12. *buf2 = tmp;
  13. buf1++;
  14. buf2++;
  15. }
  16. }
  17. void bubble_sort(void* base, int sz, int width, int(*cmp)(void*, void*))
  18. {
  19. int i = 0, j = 0;
  20. for (i = 0; i < sz - 1; i++)
  21. {
  22. for (j = 0; j < sz - 1 - i; j++)
  23. {
  24. if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
  25. {
  26. swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
  27. }
  28. }
  29. }
  30. }
  31. void test()
  32. {
  33. int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
  34. int sz = sizeof(arr) / sizeof(arr[0]);
  35. bubble_sort(arr, sz, sizeof(arr[0]), compare);
  36. int i = 0;
  37. for (i = 0; i < sz; i++)
  38. {
  39. printf("%d ", arr[i]);
  40. }
  41. }
  42. int main()
  43. {
  44. test();
  45. return 0;
  46. }

我们这里先换成整型数组,而我们主要是实现一个qsort函数,所以大多数是不需要变的。那我们现在将我们要实现的qsort函数取名为bubble_sort。因为我们只需要让他帮我们排个序,不需要返回什么,所以返回类型就是void。那既然是一个函数,要调用这个函数就需要传递参数吧。

先来看第一个参数,当我们在test函数内部调用这个函数的时候,bubble_sort函数知道我们传的是什么类型的数组吗?不知道!那如果我们拿诸如  int*,float*  之类的指针接收该地址的话,那不就没法实现传什么类型都能排序的初衷了吗?但换一个角度来看,如果我们拿void*来接收会不会更好一点呢?当我们用void*来接收之后,虽然其并不能解引用,但我们可以在回调函数这步时将其强制类型转换,不就可以进行解引用了吗。  第一个参数如下:

  1. void bubble_sort(void* base, , , )
  2. {
  3. //语句
  4. }

而第二个与第三个函数我们可以模仿一下 qsort 函数,第二个传的是数组的元素个数,sz,第三个传的是每个元素的大小,我们也可以将其称之为 width ( 宽度 )。而我们传过去的话,我们可以用int接收。sz可以用在for循环嵌套中,而width可以用于比较所需排序的数组中的每一个元素,这个在后面会讲到

而至于第四个参数,因为他是传一个函数进来,所以我们就需要拿一个函数指针来接收。但我们不知道他传过来的是一个什么类型的数,有可能是int,float或其他。所以这个参数我们得写 void*,然后在回调函数的时候只需要强制类型转换一下就能得到返回值啦,之后再根据返回值来判断应不应该交换。由此,我们就能得到如下代码:

  1. void bubble_sort(void* base, int sz, int width, int(*cmp)(void*, void*))
  2. {
  3. //实现过程
  4. }

接下来我们来看,既然是冒泡,那我们依然可以用for循环嵌套来表示有多少趟冒泡和每一趟冒泡。如下:

  1. void bubble_sort(void* base, int sz, int width, int(*cmp)(void*, void*))
  2. {
  3. int i = 0, j = 0;
  4. //表示有多少趟冒泡
  5. for (i = 0; i < sz - 1; i++)
  6. {
  7. //表示每一趟冒泡
  8. for (j = 0; j < sz - 1 - i; j++)
  9. {
  10. //判断与交换
  11. }
  12. }
  13. }

那么既然已经把冒泡给表示出来了,那么接下来我们就进入判断环节

可是问题来了,我们该怎么判断呢?我们接收的起点被命名为base,也就是那个数组的首元素地址。可是我们是拿void*来接收的,void*又不能进行加减运算,不加减运算我们也没办法找到下一个要比较的数,这可怎么办呢?

不知各位看到这里的时候有没有发现我们的width还没有被用到啊?width就是这个需要排序的数组里的每一个元素占了多少个字节。试想一下,我们先将base强制类型转换成(char*),char*每次加一的时候只会跳过一个字节,我们需要跳过多少个字节我们才能找到下一个字节我们知道吗?知道!width个字节。我们如果要表示每趟冒泡里要交换的数,我们只需要让其加上 j*width就行了(第二层for循环里的是 j ),前一个是(char*)base+j*width,后一个是(char*)base+(j+1)*width

那如果我们要判断的话,我们就需要再次调用我们接收的第四个参数, 让其返回一个小于或大于0的整数,或者是两数相等的话就返回0。接下来就让其判断:如果返回值大于0就进行下一步。毕竟我们无法判断其是要从小到大排序还是其他顺序,其所需要的排序方式应该在传过来的那个函数里被写好,由此,我们就能得到如下代码:

  1. void bubble_sort(void* base, int sz, int width, int(*cmp)(void*, void*))
  2. {
  3. int i = 0, j = 0;
  4. for (i = 0; i < sz - 1; i++)
  5. {
  6. for (j = 0; j < sz - 1 - i; j++)
  7. {
  8. //( cmp ( 参数一 , 参数二 ) >0)
  9. //需要比较的两个数作为参数
  10. if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
  11. {
  12. //交换
  13. }
  14. }
  15. }
  16. }

接下来我们就进入了交换这个环节,这里我们可以写一个函数来分装,姑且叫他swap函数好了。

但问题来了,既然要调用这个函数我们总得传参吧,那我们传什么过去好呢?有人可能会说传(char*)base + j * width 和 (char*)base + ( j + 1) * width就行了,真是如此吗?

我们且看,我们传过去的是一个char类型的地址,那swap函数就应该拿一个char*来接收吧,那接收完之后,就该开始交换了。可是,我们要交换的那个元素是几个字节的我们知道吗?我们不知道,而且我们交换的话,我们要一个字节一个字节地交换,我们要交换几个字节我们知道吗?我们发现我们又不确定了。

但,如果我们多传一个width会怎么样?我们就会知道我们需要传width个字节,而一个一个地传的话,我们可以用一个for循环,令其判断条件为从0开始并小于width,这样我们就实现了交换这个过程。由此,我们得出如下代码:

  1. void swap(char* buf1, char* buf2, int width)
  2. {
  3. int i = 0;
  4. for (i = 0; i < width; i++)
  5. {
  6. //交换的过程
  7. }
  8. }

那我们接下来我们来实现一下交换的过程。

既然要交换的元素是用char类型接收的,那我们就用char类型定义一个变量->char tmp。而初始化的值我们可以用*buf1来赋值给tmp,这里解引用是因为传的是地址,所以要解引用之后才能使用。交换完之后我们再各自让buf1和buf2 ++一下就可以啦。注:这里的buf1和buf2++并不需要解引用,因为地址++了之后指向的就是下一个字节(char类型)。由此我们可以得出以下代码:

  1. void swap(char* buf1, char* buf2, int width)
  2. {
  3. int i = 0;
  4. for (i = 0; i < width; i++)
  5. {
  6. char tmp = *buf1;
  7. *buf1 = *buf2;
  8. *buf2 = tmp;
  9. buf1++;
  10. buf2++;
  11. }
  12. }

至此,我们今天的内容也就结束了!!

如果觉得分享的内容对各位有帮助的话,就请多多关注咯!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/806086
推荐阅读
相关标签
  

闽ICP备14008679号