冒泡排序
在讲解之前,我们先来搞清楚,冒泡排序究竟是怎样的。什么叫冒泡排序呢?冒泡排序的核心思想:两两相邻的元素比较,不满足顺序就交换,满足顺序就找下一对。
举个例子:这里给出一组数据
我们的目标是:给这组数据完成升序排序。小的在前面,大的在后面。我们来一对一对的比较。首先等待排序的一对是9和8。我们看到,9和8现在不满足升序的要求。因此,我们把它们交换位置。交换之后就变成了
搞定这一对元素之后,比较9和7。因为不满足升序,所以给他俩也要交换位置。此时就变成了
以此类推,最后我们会得到
我们发现,比较下来,最大的数字9到了最后。我们把这一整个比较过程叫做一趟冒泡排序。一趟冒泡排序下来,我们搞定了一个元素:9。不管9刚开始在任何位置,经过一趟冒泡排序,它肯定会在最末尾。
搞定了9,剩下的是
我们再用相同的方法,对它们进行比较。首先是8和7。我们对他俩进行比较,就得到了
然后再对他们相邻的元素进行比较也就是8和6进行比较,我们可以得到
以此类推,经过多次交换,我们可以得到
此时我们可以发现,在876543210这些数中,经过反复的比较和交换,8也来到了最后。我们可以得出:一趟冒泡排序可以解决一个元素的排序。假如我们给出十个元素,那要进行多少趟排序呢?答案是9。因为一趟解决一个,一趟解决一个,九趟之后,最小的肯定已经在最前面去了,因此这个数字不需要再比较。当有n
个元素,那就需要有(n - 1
)趟。我们给每个元素给个下标。众所周知,数组的下标是从0开始的。因此,第一次比较其实就是下标为0的元素和下标为1的元素进行比较
第二次就是下标为1的元素和下标为2的元素进行比较
如果我们有个下标j
,那就等于是把下标为j
和j+1
的元素进行比较。我们再观察一下,第一趟排序,10个元素,我们比较了9对;第二趟排序,由于已经搞定了一个元素,因此只剩下9个待排序的元素,我们对它们进行了8对比较。
经过分析,我们可以来敲代码啦~以下就是经过上述分析得到的冒泡排序代码。
#include <stdio.h>
void input(int* arr, int sz)
{
for (int i = 0;i < sz; i++)
{
scanf("%d", arr + i);
}
}
void bubble_sort(int* arr, int sz)
{
//确定趟数
int i = 0;
for (i = 0;i < sz - 1;i++)
{
//每一趟内部两个相邻的元素比较
int j = 0;
/*将arr[j] arr[j + 1]*/
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;
}
}
}
}
void print(int arr[], int sz)
{
int i = 0;
for (i = 0;i < sz;i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[10] = { 0 };
//输入一些值
int sz = sizeof(arr) / sizeof(arr[0]);//sz 表示输入的元素个数
input(arr, sz);
//排序-写一个函数完成数组的升序排序
bubble_sort(arr, sz);
print(arr,sz);
return 0;
}
这个程序实现了输入一组整数,然后使用冒泡排序对这些整数进行升序排序,最后将排序后的结果输出。让我们来分析一下这个代码:
- input函数:
- input 函数通过循环从标准输入中读取整数,并将它们存储到传入的数组 arr 中。
- 这个函数使用了指针算术 (arr + i) 来访问数组的每个元素,并通过 scanf 函数读取输入的整数值。
- bubble_sort 函数:
- bubble_sort 函数实现了冒泡排序算法。
- 冒泡排序的基本思路是,依次比较相邻的两个元素,如果它们的顺序不对则交换,这样每一趟可以将未排序部分的最大元素冒泡到正确的位置。
- 外层循环控制趟数,内层循环对每一趟进行相邻元素的比较和交换操作。
- print 函数:
- 使用循环遍历数组,并使用 printf 函数逐个输出数组元素的值
- main 函数:
- 使用 sizeof(arr) / sizeof(arr[0]) 计算出数组的大小,存储在变量 sz 中,用于确定输入的元素个数。
- 调用 input 函数从标准输入中读取输入的整数,然后调用 bubble_sort 函数对数组进行排序。
- 最后调用 print 函数输出排序后的数组。
我们来做一组测试:输入一组数据,进行排序
可见,这个程序顺利完成数据的升序排序。
然而,这种算法其实在某种情况下,会特别浪费时间。比如说:输入的数据是1 2 3 4 5 6 7 8 9 10,要求我们排成升序。我们发现,此时输入的数据已经是升序,但是程序还是会将他们挨个挨个比较进行排序。因此我们可以对程序进行一下优化:
我们在每一趟开始的时候,都进行一下判断,假设已经满足顺序。
优化的目标是在列表已经有序的情况下提前结束排序过程,而不是继续执行多余的遍历。这就是 flag 变量的作用所在:
初始化:在每一趟排序开始时,假设列表已经有序,将 flag 初始化为 1
比较与交换:在每次比较过程中,如果发现有任何一对相邻的元素需要交换,则表明列表仍然不完全有序,将 flag 设置为 0。
在内层循环结束后,通过检查 flag 的值来确定是否有元素交换过。如果 flag 保持为 1,说明整个列表已经有序,此时可以提前结束外层循环,无需继续进行后续的排序操作。
通过这种方式,当输入的数据已经基本有序时,可以避免不必要的比较和交换操作,从而提高冒泡排序的效率。
下面是优化过后的代码:
#include <stdio.h>
void input(int* arr, int sz)
{
for (int i = 0;i < sz; i++)
{
scanf("%d", arr + i);
}
}
void bubble_sort(int* arr, int sz)
{
//确定趟数
int i = 0;
for (i = 0;i < sz - 1;i++)
{
int flag = 1;//假设已经满足顺序
//每一趟内部两个相邻的元素比较
int j = 0;
/*将arr[j] arr[j + 1]*/
for (j = 0;j < sz - 1 - i;j++)
{
if (arr[j] > arr[j + 1])
{
flag = 0;//还不是有序
//交换
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
if (flag == 1)
{
break;
}
}
}
void print(int arr[], int sz)
{
int i = 0;
for (i = 0;i < sz;i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[10] = { 0 };
//输入一些值
int sz = sizeof(arr) / sizeof(arr[0]);//sz 表示输入的元素个数
input(arr, sz);
//排序-写一个函数完成数组的升序排序
bubble_sort(arr, sz);
print(arr,sz);
return 0;
}
做个测试:我们来看看输入不同的数据,各自比较了多少对数据。
然后在主函数中打印count:
代码走起来!
这里我们输入了一对已经有序的数据。可以看到,程序只进行了9对比较,也就是一趟冒泡排序。
但我们想一下,如果没有flag,会进行多少次比较:
我们可以看到,如果没有flag判断,程序对这组已经有序的数据依旧进行了45次比较。因为第一趟冒泡排序我们要进行9对判断;第二趟要进行8对;第三趟要进行7对……以此类推进行了45对比较,比较浪费时间。