文章目录
冒泡排序
冒泡排序顾名思义就是用来给数据排序的一种方法,假设有一整型数组,如果要将这个数组中的元素按从小到大或从大到小的顺序排序,就可以用冒泡排序来完成。
冒泡排序的主要做法是:两个相邻的元素进行比较,不满足条件(升序或降序)就交换,举个例子
c
int arr[5] = {3,6,2,7,5};
假设要将上面这个数组排成升序的形式,首先第一个元素和它后面相邻的元素比较,如果比它后面的元素大,就将两个元素进行交换,然后第二个元素继续和它后面的元素比较。
而如果第一个元素比第二个元素要小,则不发生交换,然后第二个元素继续和它后面的元素比较,以此类推,在遍历完一遍数组之后,数组中最大的那个元素就被排到了最后面。
然后再从数组的第一个元素开始和它后面相邻的元素比较,只不过这一次只遍历到数组的倒数第二个元素的位置即可,然后循环遍历数组,直到数组变成升序为止。
由此可知,在其余数组元素排好之后,剩下的最后一个元素必然是有序的,所以总的排序次数应当比数组元素个数少1,比如当前这个数组有5个元素,但在排好了其中的4个元素之后,剩下的一个必然也是有序的。
而每进行一趟冒牌排序,就会将当前乱序的元素中最大的一个排在最后面,再进行下一次冒泡排序时,需要比较的元素就会少1个。
下面是冒泡排序实现的代码,这里将其封装成了函数的形式
c
void bubble_sort(int arr[], int sz)//参数接收数组元素个数
{
int i = 0;
for(i=0; i<sz-1; i++)
{
int j = 0;
for(j=0; j<sz-i-1; j++)
{
if(arr[j] > arr[j+1])
{
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
在这个代码中,最外层的for循环控制的是总的冒泡排序的次数,而里层的for循环则控制的是每一次冒泡排序的比较次数,因为是一趟内部每两个元素两两进行比较,所以第一趟5个元素要比较4次,第二趟因为最大的一个已经排好了,只需要比较4个元素,即比较3次,以此类推,最开始比较的次数就是数组元素个数减1,而且每走一趟,比较的次数就要减1,所以内部的for循环条件写成了j<sz-i-1。
回调函数
回调函数是什么?
回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数
时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条
件发生时由另外的一方调用的,用于对该事件或条件进行响应。
在讲数组和指针的时候,我们实现了一个简单的计算器,代码如下
c
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
虽然代码的逻辑很简单,但是这里也有一个问题,那就是在调用函数的时候,代码是重复出现的,虽然其中执行计算的逻辑是有区别的,而上一次我们是通过函数指针数组来进行优化的,代码如下
c
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
printf("ret = %d\n", ret);
}
else if (input == 0)
{
printf("退出计算器\n");
}
else
{
printf("输⼊有误\n");
}
} while (input);
return 0;
}
其实这里我们也可以通过回调函数的方式进行优化,代码如下
c
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
void calc(int(*pf)(int, int))
{
int ret = 0;
int x, y;
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("ret = %d\n", ret);
}
int main()
{
int input = 1;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
calc(add);
break;
case 2:
calc(sub);
break;
case 3:
calc(mul);
break;
case 4:
calc(div);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
在这个代码中的calc函数就是一个回调函数,通过接收不同函数的地址,从而可以调用不同的函数,就相当于一个跳板,这样也使得程序的写法更加灵活多变。
qsort函数简介
qsort函数是C语言中的一个库函数,专门用来对数据进行排序的,而且是可以排任意类型的,包含在stdlib.h的头文件中。
qsort函数的使用
先来看一下qsort这个函数长什么样?
这里我们可以看到qsort这个函数有4个参数,那这4个参数都是啥意思呢?我们来看一下下面的解释
总结一下就是:base是指向待排序数组的第一个元素的指针、size_t num是base指向数组的元素个数、size_t size是base指向数组中一个元素的大小,单位是字节、compare指的是函数指针,用来传递函数地址,而compar函数对返回值的要求是当p1指向的元素比p2指向的元素大时,返回大于0的数,当p1指向的元素比p2指向的元素小时,返回小于0 的数,相等则返回0。要注意的是这个compar函数是要我们自己来写的!至于为什么要我们自己来写,我们可以类比刚开始我们写的冒泡排序
在这个代码中我们可以看出来,不管是要排什么类型,除了圈出来的这部分两两比较的代码之外,其余的东西都是不用变的,我们这里是排整型,如果要排浮点型,只需要把tmp的类型换成float或double就行了
由此我们也可以联想到qsort函数既然能排所有的类型,要两两比较的compar函数是要我们自己来写也就不难理解了
这里我们用qsort函数将一组乱序的整型数组排成升序
c
#include <stdio.h>
#include <stdlib.h>
int compar(const void* p1, const void* p2)
{
return *(int*)p2 - *(int*)p1;
}
void print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
void test()
{
int arr[] = { 1,5,7,6,3,9,4,8,0,2 };
int len = sizeof(arr) / sizeof(arr[0]);
print(arr, len);//排序之前
qsort(arr, len, sizeof(arr[0]), compar);
print(arr, len);//排序之后
}
int main()
{
test();//这里用test函数是为了看起来更清晰和美观
return 0;
}
这里使用qsort函数时并没有直接将代码写在主函数里面,一是为了好看,二是为了看起来更清晰直观。
要注意在给qsort函数传参的时候,最后一个参数传我们实现好的compare函数的函数名就行,形参用对应的函数指针来接受,方便qsort函数内部调用这个比较函数对数组的其中两个元素进行比较,还有就是这里的print函数也可以不写,主要是为了方便看排序的结果,用调试看也一样
qsort函数的模拟实现
这里我们就用冒泡排序的方法来模拟实现qsort函数
c
#include <stdio.h>
int compar(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
void print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
void Swap(char* p1, char* p2, size_t len)
{
while (len--)
{
char tmp = *p1;
*p1 = *p2;
*p2 = tmp;
p1++;
p2++;
}
}
void my_qsort(void* base, size_t num, size_t size, int (*compar)(const void*, const void*))
{
int i = 0;
//躺数
for (i = 0; i < num - 1; i++)
{
int j = 0;
//一趟内部的比较
for (j = 0; j < num - 1 - i; j++)
{
if (compar((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
//交换
Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
void test()
{
int arr[] = { 5,2,6,3,9,1,8,4,7,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
//qsort(arr, sz, sizeof(arr[0]), compar);
print(arr, sz);
my_qsort(arr, sz, sizeof(arr[0]), compar);
print(arr, sz);
return 0;
}
int main()
{
test();
return 0;
}
可以看到我们模拟实现的qsort函数和冒泡排序大体结构是一样的,只不过是把原本在冒泡排序内部进行的两两比较的代码抽离出来分装成了一个函数,让用的人自己实现,从而可以排任意类型的数据。
从函数的参数能知道要排序的这个数组的首元素的指针、元素个数、每个元素的大小和compar函数的地址,所以在实现qsort函数内部调用compar函数的时候可以以字节为单位,这样可以确保我们实现的qsort函数是可以排序任意类型的,在传参的时候我们把要比较的两个元素的首地址传强转为字符指针传过去就可以了。
而compar函数的参数是const void*类型的指针,可以接收任意类型的指针,const修饰的是指向的内容,因为不期望改变指针指向的内容。
这里将交换两个变量的代码也分装成了一个函数,跟前面一样,传参的时候也要强转成字符指针类型,再将要比较的元素的地址传过去,而在Swap函数内部交换时,因为知道一个元素的大小是几个字节,所以可以一个字节一个字节来交换。
到这里我们模拟实现的qsort函数就算是写完了!