背景:
冒泡排序(Bubble sort),是一种交换排序,核心是冒泡,把数组中最小的那个往上冒,冒的过程就是和他相邻的元素交换。
过程:
重复走访要排序的数列,通过比较相邻的数来交换位置。在升序情况下如果比你大,那就交换,否则不换。第一次比较结束后,比较双方往后走一步,继续重复上述流程直到比较完一趟
以下面数据为例
3 2 比较
发现3>2,互换数据
3,7比较,3<7,不换,双方移动一步,变成7,4比较
7>4,互换,移动
7>1互换,移动但发现已无数据,故第一趟结束
代码实现:
冒泡排序
void Bubble_qsort(int arr[10], int x)
{
for (int i = 0; i < x - 1; i++)
{
for (int j = 0; j < x - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int size = sizeof(arr) / sizeof(arr[0]);
Bubble_qsort(arr,size);
int i = 0;
while (i <= 9)
{
printf("%d",arr[i]);
i++;
}
return 0;
}
这里说明一下代码逻辑:很多人不明白为什么要用双重循环。这是因为,第一躺比较中你需要把数据中最大的一项找出来,这就需要不断的比较,此乃一重循环;当你把数据中最大的一项找出来后问题还没有结束,因为其余的数据还不一定有序,你需要把其余数据也变得有序才行,这就需要你进行第二趟循环。在第二趟循环中,你要找出剩余数据的最大项,请注意!第一遍已经找出最大项并把它放在最后了,你此时找的是第二大项,无需再比较最后的那个最大项了。
你看,1躺找一个剩余数据中的最大项,为了找完所有最大项,是不是要进行一个循环?每一躺里要比较多次,这是不是又是一个循环?两个叠在一起不就是双重循环吗!
思考:
现在已经讲完了冒泡排序,可是我们也发现了一些问题。冒泡好像只能比较整型数据,换成字符串好像就没那么容易比较出大小了。此外,就算你改进冒泡,让它可以比较字符串,但是浮点数、结构体等等是不是还比较不了?解决了一个问题,会导致原本的数据排不了。由此可见,冒泡排序的适用性非常小,我们需要新的排序来满足我们的要求。
导语:
冒泡排序加强版,顾名思义,就是底层逻辑一样,但功能上可以比较不同数据类型。
代码思想:
无法解决不同数据类型的比较是因为一开始我们代码的数据类型就是确定的,是固定死的。你用数组做模板满足不了结构体的比较逻辑。因此,我们借用指针来指向数据类型,通过改变指针的指向来改变数据进而满足要求。
#include<stdio.h>
#include<stdlib.h>
//冒泡排序升级版
void Swap(char* buf1, char* buf2, int size)//交换arr[j],arr[j+1]这两个元素
{
int i = 0;
char tmp = 0;
for (i = 0; i < size; i++)
{
tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void Bubble_sort(void*arr,int num,int size, int (*cmp)(const void*, const void*))
{
for (int i = 0; i < num - 1; i++)
{
for (int j = 0; j < num - 1-i;j++)
{
if (cmp((char*)arr + j * size, (char*)arr + (j + 1) * size) > 0)//两个元素比较,需要将arr[j],arr[j+1]的地址要传给cmp
{
Swap((char*)arr + j * size, (char*)arr + (j + 1) * size, size);
}
}
}
}
struct Stu
{
char name[20];
int age;
};
int cmp_int(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
int cmp_stu_by_age(const void* p1, const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->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);
}
void test2()//测试结构体的年龄数据
{
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_age);
}
test1()//测试整型数据
{
int arr[10] = { 3,4,5,7,8,9,1,2,0,6 };
int num = sizeof(arr) / sizeof(arr[0]);
int size = sizeof(int);
bubble_sort(arr, num, sizeof(arr[0]), cmp_int);
print(arr,num);
}
int cmp_int(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
int main()
{
//test1();
//test2();
test3();
return 0;
}
注意事项:
难点在于如何比较,和如何交换。
字符串怎么比大小?结构体中的年龄怎么比?比的过程中要注意什么?
解决:首先请阅读qsort函数的定义
我们用回调函数来解决比较的问题,return 的返回值决定了顺序,一定要小心。比较过程中要注意格式转换,以及不同数据的大小(移动一步要加几?)
交换时也要注意格式转换,以及变量应该是指针而不是具体的数据类型。
结语:
通过这一篇文章,我们了解了冒泡排序及其加强版。其中有一点需要格外注意。
它是,在发现数据类型被固定死,改变这种类型又丢掉了那种类型,顾此失彼时,我们可以运用指针来解决。我对指针的理解是,指针是死的,但是它指向的数据却是活的,它就像一个传送器,当你前路不通时,借助指针,你能到达想去的远方。
我是happy sky,编程之路你我一起探索。