目录
题目:(排序问题)
解法1:
冒泡排序法
解法2:
选择排序法
解法3:
使用库函数qsort
1、先了解库函数qsort
2、使用qsort函数
解法4:
模仿库函数qsort
对qsort拓展:
1、使用qsort对结构体进行排序
2、模仿qsort对结构体进行排序
回调函数(初步认识):
对10个整数进行排序(升序)
冒泡排序法的思路:每次将相邻两个数比较,将小的放在前面(两两比较)。
假设有6个数:9,8,5,4,2,0
由图可知,大数”沉底“,小数”上升“,就如同水底的气泡逐步冒出水面一样,因此,称为冒泡法或起泡法。
当我们需要给n个数据排序时,第一次两两比较就要比较n-1次,一共要比较n-1趟,每比较一趟,两两比较的数据也会随其减少一个,则令 i 控制趟数,循环n-1次,用 j 和 j+1号来进行两两比较。
代码如下:
#include
int main()
{
int num[10] = { 0 };
int i = 0;
int j = 0;
int tmp = 0;
printf("input 10 numbers:");
for (i = 0; i < 10; i++)
{
scanf("%d", &num[i]);
}
for (i = 0; i < 10 - 1; i++)
{
for (j = 0; j <10- i - 1; j++)
{
if (num[j] > num[j + 1])
{
tmp = num[j];
num[j] = num[j + 1];
num[j + 1] = tmp;
}
}
}
for (i = 0; i < 10; i++)
{
printf("%d ", num[i]);
}
return 0;
}
运行截图:
代码优化:
上面的代码在运行时,即使是一组已经有序的数据,他还是会傻傻的走完流程,依次比较一番。
在下面的代码中,用flag当作一个标记,令flag=1,假设他是一组有序的数据,当他进入if,开始交换时,代表这组数据不是有序的,则将flag赋值为0,当一轮比较结束,先判断flag是否等于1,如果等于1,则代表他是一组有序的数据了,则跳出循环。
代码如下:
#include
int main()
{
int num[10] = { 0 };
int i = 0;
int j = 0;
int tmp = 0;
printf("input 10 numbers:");
for (i = 0; i < 10; i++)
{
scanf("%d", &num[i]);
}
for (i = 0; i < 10 - 1; i++)
{
int flag = 1;
for (j = 0; j <10- i - 1; j++)
{
if (num[j] > num[j + 1])
{
tmp = num[j];
num[j] = num[j + 1];
num[j + 1] = tmp;
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
for (i = 0; i < 10; i++)
{
printf("%d ", num[i]);
}
return 0;
}
选择排序法的思想:
1、在给定的一组数据中,找到最小的数,放在数据的起始位置
2、在剩余的数据中,找到最小的数,放在上一次最小数的末尾
3、依次执行,至全部数据排序完毕
代码如下:
#include
int main()
{
int num[10] = { 0 };
int i = 0;
int j = 0;
int min = 0;
int tmp = 0;
printf("input 10 numbers:");
for (i = 0; i < 10; i++)
{
scanf("%d", &num[i]);
}
for (i = 0; i < 10; i++)
{
min = i;
for (j = i+1; j < 10; j++)
{
if (num[min] > num[j])
{
min = j;
}
}
tmp = num[i];
num[i] = num[min];
num[min] = tmp;
}
for (i=0; i < 10; i++)
{
printf("%d ", num[i]);
}
return 0;
}
代码解释:
例如,在第一轮中,在for(j=0;...)这个循环中,所有数进行比较,找到最小的数,把它存放在num[i]中,此时i=0的,即num[0]已确定;
第二轮,在for(j=1;...)这个循环中,剩余数进行比较,找最小数,把它放在num[i]中,此时i=1的,即num[1]已确定;以此类推。
最后输出即可。
通过MSDN可查找qsort函数
函数说明:
(1、qsort是使用快速排序思想实现的排序函数
(2、qsort——>这个函数可以排序任意的数据
(3、函数:
void qsort(
void* base,
size_t num,
size_t width,
int(_cdecl* compare)(const void* elem1,const void* elem2)
);
(4、使用qsort需要包含头文件
(5、理解:
将qsort函数的参数,拆分下来;
void qsort(void* base,——>你要排序的数据的起始位置
size_t num,——>待排序的数据元素个数
size_t width,——>待排序的数据元素的大小(单位:字节)
int(* cmp)(const void* e1,const void* e2)——>函数指针-->比较函数
);——>一共四个参数
_cdecl--->函数调用约定(可以删掉不看,便于理解);另外更改的几个名称都是自定义的一个名字,我们简化一下便于理解。
(5.1、void* base :qsort函数在设计时,并不知道未来使用者在使用qsort函数时,给什么类型的数据进行排序,所以在接收数据的起始位置时,不能确定具体的类型,而又必须要有数据的起始位置,因此,采用void*来接收最为合适(void*是可以接收任意类型数据的地址)。
(5.2、size_t num:给数据排序,当然需要知道数据的个数呀!所以,这里传参中需要传数据个数
(5.3、 size_t width:因为我们知道了数据的起始位置和元素个数,但是并不知道每个元素的字节数,所以将每个元素的宽度传过来。也就是说,我们收到了起始地址,知道了元素个数,和每个元素的字节数,我们就可以将这组数据的每个元素找到,后续再对他们进行一一比较即可。
(5.4、int(* cmp)(const void* e1,const void* e2):不同类型的数据,比较的方法就会不同,在这里我们是将自定义的比较函数的地址传递给了另一个函数qsort
注意:
(1、qsort函数中规定,自定义的比较函数,如果e1=e1,返回0,e1>e2,返回>0的数,e1
(2、比较函数是我们在使用时,根据数据类型自己来编写的一个函数
(3、void *的指针是不可以直接进行解引用操作的,下面举例(仔细看注释哈!):
#include
int main()
//{
// int a = 10;
// char* pa = &a;//类型不兼容,一般编译器中都会报警
// void* pa1 = &a;//正常运行,void* 没有具体类型的指针,他可以接受任意类型的地址(垃圾桶)
// // 但也因为void* 没有具体类型的指针,所以他不能进行解引用,也不能+-整数的操作
// //类型不同+-整数所跳过的字节不同
// return 0;
//}
代码如下:
#include
#include
int cmp_int(void* e1, void* e2)
{
return *(int*)e1 - *(int*)e2;
}
int main()
{
int num[10] = { 0 };
int i = 0;
printf("input 10 numbers:");
for (i = 0; i < 10; i++)
{
scanf("%d", &num[i]);
}
qsort(num, 10, sizeof(num[0]),cmp_int);
for (i = 0; i < 10; i++)
{
printf("%d ", num[i]);
}
return 0;
}
代码解释:
在代码中,e1,e2是指向两个整数,而com_int函数是用void* 接收的,所以在比较大小时,需要强制类型转换为int *,然后进行解引用,比较数值大小 。
这时有人可能会疑惑,那我给他传参,传整型不就好了吗?但是一定要注意qsort函数的定义中,要求的是,比较函数在传参时,传的是void*,因为qsort函数,是可以给任意类型的数据排序的,所以无法预知待排序的数据类型,因此使用void*,使用时,强制类型转换一下即可。
代码如下:
#include
void swap(int* e1, int* e2)
{
int tmp = *e1;
*e1 = *e2;
*e2 = tmp;
}
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void bubble_sort(void* base, int sz, int width, int(* cmp_int)(const void* e1,const void* e2))
{
int i = 0;
int j = 0;
for (i = 0; i < sz-1; i++)
{
int flag = 1;
for (j = 0; j < sz - 1 - i; j++)
{
if (cmp_int((int*)base+j ,(int*)base+(j + 1))>0)
{
//交换
swap((int*)base + j , (int*)base + (j + 1));
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
int main()
{
int num[10] = { 0 };
int i = 0;
printf("input 10 numbers:");
for (i = 0; i < 10; i++)
{
scanf("%d", &num[i]);
}
bubble_sort(num,10, sizeof(num[0]), cmp_int);
for (i = 0; i < 10; i++)
{
printf("%d ", num[i]);
}
return 0;
}
代码解释:
我们在这里,大体没有改变,就是将qsort这样一个以快速排序为思想的函数,更换为一个自定义的使用冒泡排序的思想的函数 ,并且将其过程详细的呈现出来。
而在冒泡排序中,我们继续使用原来冒泡排序的大体框架,只是将需要交换顺序的地方修改了一下。强制类型转换就不再强调了,在交换时,我在这里时有自定义了一个函数swap,当然也可以不要这个函数,直接实现交换也不影响的哈!这样写代码,只是让大家能够更好的分板块去理解我们模拟的这个函数。
即if里面也可以换一种写法,就不需要swap这个函数了,代码:
if (cmp_int((int*)base+j ,(int*)base+(j + 1))>0)
{
//交换
//swap((int*)base + j , (int*)base + (j + 1));
int tmp = *((int*)base + j);
*((int*)base + j )= *((int*)base + (j + 1));
*((int*)base + (j + 1)) = tmp;
flag = 0;
}
再补充一点,关于+j的这个问题,因为我们是先将base强制类型转换为int*的,所以加 j 就是就是跳过了4个字节 。
运行结果都是一样的:
上面的方法都是对整型数据进行排序,下面我们将使用qsort函数,对结构体数据进行排序。
#include
#include
#include
struct stu
{
char name[20];
int age;
char addrss[10];
};
int cmp_name(const void* e1, const void* e2)
{
return strcmp((*(struct stu*)e1).name, (*(struct stu*)e2).name);
}
int cmp_age(const void* e1, const void* e2)
{
return (((struct stu*)e1)->age, ((struct stu*)e2)->age);
}
int cmp_address(const void* e1, const void* e2)
{
return strcmp((*(struct stu*)e1).addrss, (*(struct stu*)e2).addrss);
}
int main()
{
struct stu s[] = { {"a张三",20,"e西安"},{"e四",30,"c上海"},{"d王五",29,"d北京"} };
int sz = sizeof(s) / sizeof(s[0]);
int i = 0;
qsort(s, sz, sizeof(s[0]), cmp_name);
printf("名字排序:\n");
for (i = 0; i < sz; i++)
{
printf("%5s %3d %7s\n", s[i].name, s[i].age, s[i].addrss);
}
qsort(s, sz, sizeof(s[0]), cmp_age);
printf("\n年龄排序:\n");
for (i = 0; i < sz; i++)
{
printf("%5s %3d %7s\n", s[i].name, s[i].age, s[i].addrss);
}
qsort(s, sz, sizeof(s[0]), cmp_address);
printf("\n地址排序:\n");
for (i = 0; i < sz; i++)
{
printf("%5s %3d %7s\n", s[i].name, s[i].age, s[i].addrss);
}
return 0;
}
代码解释:
(1、先定义一个结构体,然后给结构体初始化
(2、 给结构体中的数据排序,首先需要知道,我们是给哪一组数据排序的,比如,最开始我是给姓名name这组数据排序的,所以我设置的比较函数为com_name(给自定义函数命名是,最好是有意义的名字),在函数中,先将数据强制类型转换为struct stu*类型,切记不要转换为char,int等类型了。
(3、字符串比较大小,是需要用到strcmp函数,同时呢,strcmp函数的返回值同qsort中所需要的函数返回值一致,因此调用strcmp后,将他的值直接返回即可。
(4、后续我又将结构体中的其他两组数据进行比较,方法同【2】中一样的,但是需要注意的是,在比较年龄age时,他是一组整型数据的比较,所以就不能使用strcmp函数,将其强制类型转换后,直接相减,返回即可。
运行结果:
注:
(1、我在名字前面加字母,只是为了让大家更加清晰的看到数据是经过排序了的
(2、打印时,最好给格式控制符加上指定的宽度,如%5d,为了美观
(3、结构体 的定义一定要放在代码的最前面
#include
#include
struct stu
{
char name[20];
int age;
char addrss[10];
};
void swap(char* e1, char* e2,int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *e1;
*e1 = *e2;
*e2 = tmp;
e1++;
e2++;
}
}
int cmp_name(const void* e1, const void* e2)
{
return strcmp(((struct stu*)e1)->name ,((struct stu*)e2)->name);
}
int cmp_age(const void* e1, const void* e2)
{
return (((struct stu*)e1)->age, ((struct stu*)e2)->age);
}
int cmp_address(const void* e1, const void* e2)
{
return strcmp(((struct stu*)e1)->addrss, ((struct stu*)e2)->addrss);
}
void bubble_sort(void* base, int sz, int width, int(*cmp_char)(const void* e1, const void* e2))
{
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)
{
int flag = 1;
for (j = 0; j < sz - 1 - i; j++)
{
if (cmp_char((char*)base + j*width, (char*)base + (j + 1)*width) > 0)
{
//交换
swap((char*)base + j*width, (char*)base + (j + 1)*width,width);
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
int main()
{
int i = 0;
int j = 0;
struct stu s[] = { {"a张三",20,"e西安"},{"e四",30,"c上海"},{"d王五",29,"d北京"} };
int sz = sizeof(s) / sizeof(s[0]);
bubble_sort(s, sz, sizeof(s[0]), cmp_name);
printf("名字排序:\n");
for (i = 0; i < sz; i++)
{
printf("%5s %3d %7s\n", s[i].name, s[i].age, s[i].addrss);
}
bubble_sort(s, sz, sizeof(s[0]), cmp_age);
printf("\n年龄排序:\n");
for (i = 0; i < sz; i++)
{
printf("%5s %3d %7s\n", s[i].name, s[i].age, s[i].addrss);
}
bubble_sort(s, sz, sizeof(s[0]), cmp_address);
printf("\n地址排序:\n");
for (i = 0; i < sz; i++)
{
printf("%5s %3d %7s\n", s[i].name, s[i].age, s[i].addrss);
}
return 0;
}
代码解释:
(1、 大致思路和我们上写的给整型数据排序的思路是一样的,利用的是冒泡排序的思路
(2、里面我们自定义的几个函数,如com_name等,同上面我们使用qsort函数排序时自定义的函数是一样的
(3、不同点在于,冒泡排序中,比较数据大小时,传参有所不同。
(3.1、我们给外部的比较函数在传参时,回忆一下我们在给整型数据排序时,其参数我们是先将void*类型强制类型转换为int*的类型(+-1跳4个字节),然后再+ -j,就将两个数传过去,比较大小了。
(3.2、但是,在给结构体排序时,它里面的数据可能是整型也可能是字符型,浮点型的数据等,所以,就不能只是简单的将它强制类型转换为某种类型,而需要充分利用,我们的bubble_sort函数的参数中,传递过来的元素宽度。先将它强制类型转换为char*类型(+-1跳过1个字节),然后在+ -j时,给j成乘上他的宽度。
(char*)base + j*width
(char*)base + (j + 1)*width
if(cmp_char(char*)base + j*width, (char*)base + (j + 1)*width)>0)
这一行就相当于之前冒泡排序中:
if(num[j]>num[j+1])
(3.3、接下来就是交换两个元素,我们将两个元素的起始位置传过去,同时也要将两个元素的宽度传过去(因为我们不知道我们要排序的数据的每个元素占几个字节),然后将每对字节交换,则这两个元素也就交换了。
swap((char*)base + j*width, (char*)base + (j + 1)*width,width);
void swap(char* e1, char* e2,int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *e1;
*e1 = *e2;
*e2 = tmp;
e1++;
e2++;
}
}
定义:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另一方调用的,用于对该事件或条件进行响应。
在上述的代码中,我们都在使用回调函数。 上述代码中,如cmp_int , cmp_name等等都是将他们的函数名作为参数,将这个函数传给qsort或bubble_sort函数,通过qsort或bubble_sort函数来实现对cmp_int , cmp_name等函数的调用。即在这里,cmp_int , cmp_name等函数被称为回调函数。