目录
四.用冒泡排序思想,模拟实现 qsort (可排序任意类型数据)
一.冒泡排序
对数组进行排序,升序。思想:两两相邻元素比较
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 - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int 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);
//打印 0 1 2 3 4 5 6 7 8 9
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
10个元素9趟,sz 个元素 sz-1 趟。
第一趟10个元素比较9次,第二趟9个元素比较8次 ......
缺点:只能排序整数。因为他的参数已经写死了,只能是整形数组。想排序字符数组、浮点型、结构体......做不到。他的功能非常有限。
二.指针类型 void*
先跳过,看完 三.2.后,回来看。
为什么 qsort 的参数写的 void*(无具体类型的指针) ?
因为:函数设计者,不知道未来你会比较什么类型的数据。
int a = 10;
float f = 5.5f;
int* p = &a;//编译器没报错
p = &f;//编译器报错:从“float *”到“int *” 的类型不兼容
void* pp = &f;//不报错
pp = &a;//不报错
可以把 pp 理解为通用的指针。(垃圾桶)谁的地址往进放。
缺点:放进去,不能直接用
float f = 5.5f;
void* pp = &f;
printf("%f", *pp);
void* 类型的指针不能直接解引用。它没有具体类型,自己都不知道自己是啥类型
pp++; 也不行。无具体类型,它也不知道它 + 一步,+ 多远
当你都不知道未来别人给你传谁的地址时,用 void* 类型的指针接收
怎么用?指针类型可以转换
接着看 三.3.
三. qsort
qsort 内部的细节大家先不要关心,回头小编带大家模拟实现
1.简介
是库函数里提供的一个排序函数,底层用的是快速排序。头文件 #include <stdlib.h>
2.研究函数参数
一共4个参数:1.待排序数组的起始地址。2.元素个数。3.一个元素的大小(字节)。
4.(函数指针)。int (*compar)(const void* e1, const void* e2)
先分析,对上面的 bubble_sort 函数部分进行改造。希望函数可以排整型、浮点型、结构体......(任意类型数据),但还是用冒泡排序的思想,趟数和一趟比较的对数都不用变。
11-15行要变,因为:两个整型可以直接用 > 比较。但两个结构体元素不能用 > 比。
不同数据的比较方法不一样,交换方式不一样
想让他排任意类型的数据,把比较两个元素的方法抽离出来。
比如:程序员A 想排序两个整型,自己提供两个整型的比较方法;想排序结构体,自己提供两个结构体的比较方法。
再把比较的方法传进来,你按照我的方法比较这两个元素就可以。
其实,qsort 就是这么实现的。自己写个比较两个东西函数,把函数的地址传给 qsort ,恰好用(*compar)这个函数指针接收。然后 qsort 里面就按照这个函数指针指向的函数来比较。注意:&函数名 函数名 都是地址
写的函数就被称为回调函数
e1 e2 分别是要比较的两个元素的地址
3.怎么用?
(1)排数组,升序
#include <stdlib.h>
cmp_int()
{ }
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);// 这里是函数cmp_int 的地址
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
要把 cmp_int 的地址传给 qsort 所需要的(*compar) 函数指针。
cmp_int 的参数、返回类型要与 qsort 的第四个参数的函数指针的参数、返回类型 保持一致
#include <stdlib.h>
//实现一个比较整形的函数
int cmp_int(const void* e1, const void* e2)
{
//(int*)e1 把e1当成整型指针了
return *(int*)e1 - *(int*)e2;
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);// 这里是函数cmp_int 的地址
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
cmp_int 实现了比较 e1 e2 指向的两个整型。把 cmp_int 的地址传给 qsort
qsort 就知道了,说:你让我 比较 arr 里 sz 个元素,每个元素大小为 sizeof(arr[0]),数组arr里两个元素的比较方法是 cmp_int
想降序:return *(int*)e2 - *(int*)e1;
(2)排序结构体
1.按照学生年龄排序
#include <stdlib.h>
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_age(const void* e1, const void* e2)
{
// (struct Stu*)e2 - e2强制转化为结构体类型指针
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int main()
{
struct Stu s[3] = { {"zhangsan",20}, {"lisi", 50}, {"wangwu", 33} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
return 0;
}
2.按照学生名字排序
#include <stdlib.h>
#include <string.h>
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
int main()
{
struct Stu s[3] = { {"zhangsan",20}, {"lisi", 50}, {"wangwu", 33} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
return 0;
}
四.用冒泡排序思想,模拟实现 qsort (可排序任意类型数据)
1.函数参数设计
参数1:为了能够接受任意类型地址,用 void* 。 参数2:元素个数
为什么 qsort 要传元素大小?
void* 无具体类型,只知道待排序数组元素个数,不知道一个元素占几个字节
参数3:宽度 参数4:提出比较方法
类型最好给 size_t ,个数、宽度永远是正数
//实现一个比较整型的函数
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void bubble_sort(void* base, size_t sz, size_t 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() > 0)
{
//交换
}
//if (arr[j] > arr[j + 1])
//{
// int tmp = arr[j];
// arr[j] = arr[j + 1];
// arr[j + 1] = tmp;
//}
}
}
}
void test3()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
test3();
return 0;
}
2.在 if (cmp( )>0) 怎么传参?
相邻两元素比较,传过来要是两元素的地址。比如,传9 8地址。怎么算呢?
现在不知道里面放的什么类型的元素,没法直接用下标来搞。只能通过偏移量来算。
8的地址是 base + 几。但 base 是 void* ,不能直接加。
强制类型转换。转换成什么类型?
int ? 不行,写死了。
怎么办?9的宽度是 width,从9偏移 width 个字节来到8。怎么来到8呢?
宽度 width 的单位是字节,如果把它转换成 char* 的指针,char* 的指针+width=跳过width个字节
if(cmp((char*)base, (char*)base + width) > 0) //只是第一对元素
if(cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//交换
}
if 里面是分别对应下标为 j j+1 元素的地址,分别传给 e1 e2 指针
cmp是比较函数,e1 e2 是它比较的两个元素
我们已经把待比较的两个元素的地址传给 e1 e2 。cmp 就通过这样一个指针找到它所指向的比较函数。如果比较函数返回值 >0 就交换
3.怎么交换?
给一个Swap 函数,把(char*)base + j * width , (char*)base + (j + 1) * width) 两个指针指向的元素交换。
交换这两个元素的宽度是多大呢?注意:我不知道我交换的两个元素多大。把 width 传过去
比较的时候都不知道比较谁,交换时也肯定不知道交换的是什么类型的数据,所以通过类型是没办法
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
但是注意,当我要交换两个结构体的时候。我们没办法创建出10字节的临时空间,但是可以创建一个字节的空间。
交换每一对字节,这两个元素整体也就交换了
//已经强制转换为char*,这里也不端着了,就写成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++;
}
}
五.整体代码
#include <stdio.h>
#include <string.h>
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);
}
//实现一个比较整型的函数
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
//已经强制转换为char*,这里也不端着了,就写成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++;
}
}
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* e1, const void* e2))
{
//趟数
size_t i = 0;
for (i = 0; i < sz - 1; i++)
{
//一趟冒泡排序的过程
size_t 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);
}
}
}
}
//使用我们自己写的bubble_sort函数排序整型数组
void test3()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
//使用我们自己写的bubble_sort函数排序结构体数组
void test4()
{
struct Stu s[3] = { {"zhangsan",20}, {"lisi", 50}, {"wangwu", 33} };
int sz = sizeof(s) / sizeof(s[0]);
bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
test3();
test4();
return 0;
}