目录
冒泡排序
冒泡排序的实现代码
冒泡排序的缺点
qsort函数
qsort函数
void*指针
qsort使用实例
进阶版冒泡排序
代码实现
代码分析
Bubble_Sort函数
Swap函数
既然要介绍进阶版的冒泡排序,我们就要先知道冒牌排序的实现方式,以及冒泡排序的缺点,我们才能对其进行改进。
#define _CRT_SECURE_NO_WARNINGS
#include
int main()
{
int arr[10] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
int i,j,k = 0;
int tmp = 0;
for (i = 0; i < 9; i++) //因为数组下标从0开始,到9正好是个元素
{
for (j = 0; j< 9 - i; j++) //当进行到第i次冒牌时候,前i个最大的元素已经在之前的排序中排好放在序列尾部了
{
if (arr[j] > arr[j + 1])
{
tmp = arr[j];
arr[j] = arr[j+ 1];
arr[j + 1] = tmp;
}
}
}
for (k = 0; k < 10; k++)
{
printf("%d ",arr[k]);
}
return 0;
}
有上述代码可以看出,冒泡排序只能对一种类型的数据进行排序。这次排序的数据类型是int,但我下次要排序的数据类型变成float时,我就要重新修改代码。代码的普适性不好。
那么有没有什么解决办法呢?当然是有的。而且早就被大佬们封装好放在编译器中了。这个函数就是qsort函数。
是一个基于快速排序法的函数。
void qsort(void* base, size_t num, size_t size, int(*compar)(const void* e1, const void* e2));
1. 头文件-#include
2. base-要排序数组的首地址
3.num-数组元素个数,用sizeof(arr)/sizeof(arr[0])计算
4.size-数组中每个元素所占的字节,用sizeof(arr[0])计算
5.int(*compar)(const void* e1, const void* e2)
compar是一个函数指针,指向比较两个元素所用的函数的地址,指向的函数类型为int()(const void* e1, const void* e2),这个函数要使用者自己实现
*e1和*e2是需要排序的数组中待比较的两个元素的地址,该函数的返回值需要遵循以下规则:
1. *e1>*e2,返回值大于0(返回值类型为int)
2. *e1<*e2,返回值小于0
3. *e1=*e2,返回值等于0
4.强制类型转换时,类型应该为实参的类型
void* 因为没有规定地址的大小,所以可以接受接受任意类型的指针。但正因为未规定大小,所以不能进行解引用操作,因为不知道要解引用几个字节。同理,也不能进行加(减)整数的操作。
所以在进行解引用使用时,要进行强制类型转换。转换的类型要与实参的类型相同。
里面有两个例子。test1是将结构体变量按照年龄排序,test2是将结构体变量按照名字排序。 (tips: *e1-*e2 升序排列;*e2-*e1 降序排列)
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
struct Stu //创建结构体变量
{
char name[20];
int age;
};
int cmp_stu_by_age(const void* e1, const void* e2) //自定义的比较函数1
{
//将void*类型的指针进行强制类型转换,转换的类型与实参相同,即结构体指针(Struct Stu*)
//因为访问结构体成员的特殊性,所以是如下的写法
//如果是实参是int*类型的,那就要进行解引用
//代码要写成这种形式 return *((int*)e1) - *((int*)e2);
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age; //访问结构体成员的第1种方法
//1.结构体指针->成员
}
int cmp_stu_by_name(const void* e1, const void* e2) //自定义的比较函数2
{
//比较名字就是比较字符串
//比较字符串不能用>=<,应该用strcmp函数
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
void test1()
{
struct Stu s[3] = { {"zhangasn", 20}, {"lisi", 30}, {"wangwu", 10}};
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
int j = 0;
for (j = 0; j < sz; j++)
{
printf("%s\n", s[j].name); //访问结构体成员的第2种方法 2.结构体变量.成员
}
}
void test2()
{
struct Stu s[3] = { {"zhangasn", 20}, {"lisi", 30}, {"wangwu", 10} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
int j = 0;
for (j = 0; j < sz; j++)
{
printf("%s\n", s[j].name);
}
}
int main()
{
test1();
test2();
return 0;
}
使用改进版的冒泡函数完成qsort函数的功能,使其能够对所有类型的数据进行排序。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
void Swap(char* ele1, char* ele2, int width) //一个字节一个字节的进行交换
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *ele1;
*ele1 = *ele2;
*ele2 = tmp;
ele1++;
ele2++;
}
}
void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
int i = 0;
for (i = 0; i < sz-1; i++)
{
int j = 0;
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()
{
struct Stu s[3] = { {"zhangasn", 20}, {"lisi", 30}, {"wangwu", 10} };
int sz = sizeof(s) / sizeof(s[0]);
bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
int j = 0;
for (j = 0; j < sz; j++)
{
printf("%s\n", s[j].name);
}
}
int main()
{
test();
return 0;
}
该代码的重点是Swap函数与bubble_sort函数的编写。通过bubble_sort和Swap函数实现qsort函数的功能。
void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
int i = 0;
for (i = 0; i < sz-1; i++)
{
int j = 0;
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);
}
}
}
}
base是要进行排序的目标数组的首元素地址,但是因为是void*类型的指针,我们不知道目标数组中的元素地址到底占用多少字节。
sz是数组的元素个数,使用sizeof函数求得。sz=sizeof(数组名)/sizeof(数组首元素);
sz-1就是冒泡排序要进行的趟数。
width是数组中每个元素占用的字节数。
cmp是函数指针,指向不同的cmp函数就有不同排序条件。该代码的指向的排序条件就分别是cmp_stu_by_age函数和cmp_stu_by_name函数。
难点:
为了解决不知道数组中元素地址大小的问题,我们引入了width。只要知道了数组的起始地址以及每个元素占用的字节数。就能用最小的单位(char类型,占用1字节)将每个元素的地址表示出来。例如,s[1]元素的地址就是首地址地址+1*width,即&s[1] = base+1*width
同理可得数组中任意元素的地址。&s[j] = base+j*width
将两个元素进行比较,如果前一个元素比较打,就将两元素进行交换。这就涉及到Swap函数。
void Swap(char* ele1, char* ele2, int width) //一个字节一个字节的进行交换
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *ele1;
*ele1 = *ele2;
*ele2 = tmp;
ele1++;
ele2++;
}
}
我们面临和上面一样的问题,就是不知道每个元素的地址,无法完成交换。
既然面对的问题一样,那解决问题的方法也一样。
在传参时,我们将元素的占用的字节数width也传入Swap函数中。使用最小的单位,一个字节一个字节的交换,交换的次数就是元素的字节数。当这两个元素每个字节的内容都被交换了,这两个元素也就被交换了。