目录
1、冒泡排序
void bubble_sort(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz-1; i++) {
for (int j = 0; j < sz - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = tmp;
}
}
}
}
原理:
-
bubble_sort
函数接受一个整数数组arr
和数组的大小sz
作为参数。 -
外层循环
for (i = 0; i < sz-1; i++)
控制需要进行多少轮的比较和交换。因为每一轮都会将当前未排序部分的最大元素移动到最后,所以需要进行sz-1
轮。 -
内层循环
for (int j = 0; j < sz - 1 - i; j++)
用于遍历当前未排序部分的元素。 -
在内层循环中,通过比较相邻的两个元素
arr[j]
和arr[j + 1]
,如果顺序错误(arr[j] > arr[j + 1]
),则交换它们的位置。 -
通过交换操作,每一轮都会将当前未排序部分的最大元素移动到最后。
举例讲解:
现在,让我们使用一个简单的例子来说明冒泡排序的过程。
假设我们有一个整数数组 arr = {5, 3, 8, 2, 1}
。
初始状态:
5 3 8 2 1
第一轮:
- 比较
5
和3
,它们的顺序错误,交换它们的位置。3 5 8 2 1
- 比较
5
和8
,它们的顺序正确,不需要交换。- 比较
8
和2
,它们的顺序错误,交换它们的位置。3 5 2 8 1
- 比较
8
和1
,它们的顺序错误,交换它们的位置。3 5 2 1 8
第二轮:
- 比较
3
和5
,它们的顺序正确,不需要交换。- 比较
5
和2
,它们的顺序错误,交换它们的位置。3 2 5 1 8
- 比较
5
和1
,它们的顺序错误,交换它们的位置。3 2 1 5 8
- 比较
5
和8
,它们的顺序正确,不需要交换。第三轮:
- 比较
3
和2
,它们的顺序错误,交换它们的位置。2 3 1 5 8
- 比较
3
和1
,它们的顺序错误,交换它们的位置。2 1 3 5 8
- 比较
3
和5
,它们的顺序正确,不需要交换。- 比较
5
和8
,它们的顺序正确,不需要交换。第四轮:
- 比较
2
和1
,它们的顺序错误,交换它们的位置。1 2 3 5 8
- 比较
2
和3
,它们的顺序正确,不需要交换。- 比较
3
和5
,它们的顺序正确,不需要交换。- 比较
5
和8
,它们的顺序正确,不需要交换。最终,数组已经按照升序排序完成:
1 2 3 5 8
这就是冒泡排序的过程。通过多次比较和交换,最大的元素会逐渐移动到最后,直到所有元素都排序完成。现在理解冒泡排序的工作原理了吧。接下来我们用这种思想试着实现qsort函数的功能。
2、实现qsort
回顾qsort函数
对了,如果你忘了qsort函数,来看看我之前写过的文章吧!
参数:
我们将实现相同功能的自定义函数命名为bubble_sort。
好的,现在我来开始从qsort函数所需参数入手
冒泡排序函数的参数 int 类型,那如何像qsort函数一样可以传递任何类型的参数呢?
void bubble_sort(int arr[], int sz)
我们可以看到qsort函数定义中为需要排序的变量定义成” void* “类型
void qsort(void* base, //指向了需要排序的数组的第一个元素
size_t num, //排序的元素个数
size_t size,//一个元素的大小,单位是字节
int (*cmp)(const void*, const void*)//函数指针类型
这个函数指针指向的函数,能够比较base指向数组中的两个元素);
所以我们也将冒泡排序函数的参数定义为” void* “类型。
同时,我们也模仿qsort为模拟实现相同功能的bubble_sort自定义函数给予相同的参数。
void bubble_sort(void* base, int num, int size, int (*cmp)(const void*, const void*)
这些参数对于后续我们比较大小和交换位置很有帮助。
结构 :
我们来看一下冒泡函数内部哪里需要做出调整。
void bubble_sort(void* base, int num, int size, int (*cmp)(const void*, const void*))
{
for (i = 0; i < sz-1; i++) {
for (int j = 0; j < sz - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = tmp;
}
}
}
}
两个for循环遍历传入数组所需比较的元素,无需更改。
在第二个嵌套循环内部,我们需要对传入的数组元素进行比较和调整。因为我们传递的参数应该是地址,所以需要将 arr[j]
和 arr[j + 1]
替换为地址形式,即 base + j
和 base + (j + 1)
。
比较函数:
同时,比较两个元素大小不应局限于>或<,比如:结构体就不能比较用大于小于号比较大小,
所以我们要比较不同类型参数,需换成做差相减的方法,通过结果大于或小于0来比较大小。
由此,我们采用与qsort函数中相同的方式,构建可比较整型数组的函数。
int cmp_int(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
- 在比较两个元素时,我们需要传递给
cmp
函数的是两个地址,分别是下标为j
和j + 1
的地址。由于base
的类型是void*
,我们需要进行类型转换,最好将其转换为char*
类型,因为char
类型占用一个字节。 - 为了能够处理不同类型的数据,并比较相邻两个元素的大小,参数
size
就变得非常重要。 - 我们可以通过不同类型的元素大小(字节数)来跳到下一对需要比较的元素,这样我们可以确保在比较时正确地处理不同类型的数据。我们需要将
j
乘以每个元素的大小size
,以便顺利跳到相应类型的下一对元素。
交换函数:
比较完两元素大小后,如升序排列,需将较大的数与较小的数进行交换(降序相反)。
原有结构中这种方法无法交换不同类型元素,我们考虑写一个Swap函数进行不同类型元素交换。
int tmp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = tmp;
将Swap函数的参数定义为两个需要交换的元素的地址和元素的字节数 ,
通过交换字节的方式我们可以顺利交换不同类型元素。
解释:传入的参数buf1和buf2都是char*类型地址,循环对应类型的字节大小次数(即参数size)交换一个字节后进行自增,直到交换完对应元素类型所有字节,元素交换完成。
void Swap(char* buf1, char* buf2, int size)
{
int i = 0;
char tmp = 0;
for (i = 0; i < size; i++)
{
tmp = *buf1; // 保存 buf1 指向的元素的值到临时变量 tmp
*buf1 = *buf2; // 将 buf2 指向的元素的值赋给 buf1 指向的元素
*buf2 = tmp; // 将 tmp 中保存的值赋给 buf2 指向的元素
buf1++; // 移动 buf1 的指针,指向下一个元素
buf2++; // 移动 buf2 的指针,指向下一个元素
}
}
传入时参数的形式与传入比较函数时相同。
Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
输出函数:
简单写一个输出函数,方便验证是否程序成功运行。
void Print(int arr[], int sz)
{
for (int i = 0; i < sz; i++) {
printf(" %d", arr[i]);
}
printf("\n");
}
3、排序整型数据完整版:
最后,将 比较函数cmp_int 和 交换函数Swap 嵌入bubble_sort函数内,并调用Print输出,用test1进行初始化。
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
void Swap(char* buf1, char* buf2, int size)
{
for (int i = 0; i < size; i++) {
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base,int num,int size,int (*cmp)(const void*,const void*))
{
int i = 0;
for (i = 0; i < num - 1; i++) {
for (int j = 0; j < num - 1 - i; j++) {
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
void Print(int arr[], int sz)
{
for (int i = 0; i < sz; i++) {
printf(" %d", arr[i]);
}
printf("\n");
}
void test1()
{
int arr[] = { 54,25,333,32,3 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
Print(arr, sz);
}
int main()
{
test1();
return 0;
}
成功运行,排序成功!!!
4、排序结构体数据:
void Swap(char* buf1, char* buf2, int size)
{
int i = 0;
char tmp = 0;
for (i = 0; i < size; i++)
{
tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, int num, int size, int (*cmp)(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 (cmp((char*)base+j*size, (char*)base+(j+1)*size)>0)
{
Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
struct Stu
{
char name[20];
int age;
};
void print_struct_array(struct Stu* arr, int num)
{
for (int i = 0; i < num; i++)
{
printf("Name: %s, Age: %d\n", arr[i].name, arr[i].age);
}
printf("\n");
}
int cmp_stu_by_age(const void* p1, const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void test2()
{
struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test3()
{
struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };
int sz = sizeof(arr) / sizeof(arr[0]);
printf("%d\n", sizeof(struct Stu));
bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{
test2();
test3();
return 0;
}
讲解:
-
cmp_stu_by_age函数: 这个函数是一个比较函数,用于根据学生的年龄进行比较。它在比较两个学生结构体时返回负数、零或正数,以指示第一个学生应该在第二个学生之前、相等还是之后。
-
cmp_stu_by_name函数: 这是另一个比较函数,用于根据学生的姓名进行比较。它使用
strcmp
函数来比较两个学生的姓名,同样返回负数、零或正数。 -
print_struct_array函数: 这个函数接受一个结构体数组和数组的大小作为参数,并打印数组中的每个结构体的姓名和年龄。这个函数用于输出排序后的结果。
在 main
函数中:
test2
函数对学生结构体数组按照年龄进行排序,并输出排序结果。test3
函数对同样的学生结构体数组按照姓名进行排序,并输出排序结果。
结果如下: