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

目录

1 . 基础版的冒泡排序

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

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


1 . 基础版的冒泡排序

我们先来看一下代码

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

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

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

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

如下:

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

接下来我们来重点看看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 就等于几,也就相当于有多少个数不用再被排序

代码如下:

void bubble_sort(int* arr, int sz)
{
	int i = 0;
	int j = 0;
    //第一层for循环表示有多少趟冒泡
	for (i = 0; i < sz - 1; i++)
	{
        //第二层for循环表示每一趟冒泡
		for (j = 0; j < sz - 1 - i; j++)
		{
			//语句
		}
	}
}

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

int main()
{
	int i = 0;
	int j = 0;;
    //第一层每一次循环代表一行
    //当i=1时代表第一行,i=2时代表第二行...
	for (i = 1; i <= 3; i++)
	{
        //第二层代表每一行的每个数字
        //i=1时,j=1代表第一行第一个数,j=2代表第一行第二个数...
		for (j = 1; j <= 5; j++)
		{
			printf("%d ", j);//打印结果
		}
        printf("\n");//每打印一行之后就用\n换行
	}
	return 0;
}

结果如下图所示 

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

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

void bubble_sort(int* arr, int sz)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < sz - 1; i++)
	{
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = 0;
				tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

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

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

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

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

void qsort (void* base, size_t num, size_t size,
            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 }

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

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是一个东西

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

int cmp_float(const void* e1, const void* e2)
{
	return ((int)(*(float*)e2 - *(float*)e1));
}
void test()
{
	float f[] = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 };
	int sz = sizeof(f) / sizeof(f[0]);
	qsort(f, sz, sizeof(f[0]), cmp_float);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%.1f  ", f[i]);
	}
}
int main()
{
	test();
	return 0;
}

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

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

int cmp_float(const void* e1, const void* e2)
{
    //语句
}

//假设我们定义的是这么一个数组
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*是什么呀?你可以这么去理解,

int main()
{
	int a = 1;
	int* p = &a;
	char* pc = &a;
	char* ph = (char*)&a;
	void* ps = &a;
	return 0;
}

你看,当我们这么写的时候,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 回去不就可以啦!代码如下:

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

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

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

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

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

先上代码!!

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

void swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}
void bubble_sort(void* base, int sz, int width, int(*cmp)(void*, void*))
{
	int i = 0, j = 0;
	for (i = 0; i < sz - 1; i++)
	{
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}
void test()
{
	int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), compare);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	test();
	return 0;
}

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

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

void bubble_sort(void* base,       ,       ,        )
{
    //语句
}

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

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

void bubble_sort(void* base, int sz, int width, int(*cmp)(void*, void*))
{
    //实现过程
}

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

void bubble_sort(void* base, int sz, int width, int(*cmp)(void*, void*))
{
	int i = 0, j = 0;
    //表示有多少趟冒泡
	for (i = 0; i < sz - 1; i++)
	{
        //表示每一趟冒泡
		for (j = 0; j < sz - 1 - i; j++)
		{
			//判断与交换
		}
	}
}

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

可是问题来了,我们该怎么判断呢?我们接收的起点被命名为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就进行下一步。毕竟我们无法判断其是要从小到大排序还是其他顺序,其所需要的排序方式应该在传过来的那个函数里被写好,由此,我们就能得到如下代码:

void bubble_sort(void* base, int sz, int width, int(*cmp)(void*, void*))
{
	int i = 0, j = 0;
	for (i = 0; i < sz - 1; i++)
	{
		for (j = 0; j < sz - 1 - i; j++)
		{
              //( cmp  (     参数一   ,   参数二    ) >0)
              //需要比较的两个数作为参数  
 			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//交换
			}
		}
	}
}

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

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

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

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

void swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		//交换的过程
	}
}

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

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

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

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

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

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值