Skip to content

第 24 课:冒泡排序 ​

顾名思义,这个算法的名字由来是最小的元素在不断交换中,像气泡一样慢慢“浮”到数列的顶端。先来了解一下它的概念。

24.1 冒泡排序的概念及步骤 ​

概念 ​

冒泡排序(bubble sort)是一种基础排序算法。

它会重复地走访要排序的数列,一次比较两个元素,如果它们的顺序错误,就把它们交换过来。走访数列的工作重复进行,直到再没有元素需要交换,也就是说该数列已经排序完成。

步骤(以从小到大排序为例) ​

  1. 先比较相邻的元素,如果第一个比第二个大,就交换它们两个。

  2. 对每一对相邻元素做同样的工作,从开始的第一对到结尾的最后一对,这一步做完后,最后的元素会是最大的数。

  3. 针对所有的元素重复以上的步骤,除了最后一个。

  4. 每次对越来越少的元素重复上面的步骤,直到没有任何一对数需要比较。

24.2 演示及实现 ​

演示 ​

下面以将数组 [5, 3, 8, 4, 2] 按照从小到大排序为例,详细说明冒泡排序的过程。

  1. 第一轮排序

a. 比较第 1 个和第 2 个元素(5 和 3),因为 5 > 3,所以交换它们的位置,数组变为 [3, 5, 8, 4, 2]。

b. 比较第 2 个和第 3 个元素(5 和 8),因为 5 < 8,所以位置不变,数组还是 [3, 5, 8, 4, 2]。

c. 比较第 3 个和第 4 个元素(8 和 4),因为 8 > 4,所以交换它们的位置,数组变为 [3, 5, 4, 8, 2]。

d. 比较第 4 个和第 5 个元素(8 和 2),因为 8 > 2,所以交换它们的位置,数组变为 [3, 5, 4, 2, 8]。

经过第一轮排序,最大的元素 8 已经“浮”到了数组的末尾。

  1. 第二轮排序

a. 比较第 1 个和第 2 个元素(3 和 5),因为 3 < 5,所以位置不变,数组还是 [3, 5, 4, 2, 8]。

b. 比较第 2 个和第 3 个元素(5 和 4),因为 5 > 4,所以交换它们的位置,数组变为 [3, 4, 5, 2, 8]。

c. 比较第 3 个和第 4 个元素(5 和 2),因为 5 > 2,所以交换它们的位置,数组变为 [3, 4, 2, 5, 8]。

经过第二轮排序,第二大的元素 5 也到了它最终的位置。

  1. 第三轮排序

a. 比较第 1 个和第 2 个元素(3 和 4),因为 3 < 4,所以位置不变,数组还是 [3, 4, 2, 5, 8]。

b. 比较第 2 个和第 3 个元素(4 和 2),因为 4 > 2,所以交换它们的位置,数组变为 [3, 2, 4, 5, 8]。

  1. 第四轮排序

a. 比较第 1 个和第 2 个元素(3 和 2),因为 3 > 2,所以交换它们的位置,数组变为 [2, 3, 4, 5, 8]。

代码实现 ​

cpp
#include <iostream>
using namespace std;
int main()
{
    int arr[] = {5, 3, 8, 4, 2};
    int n = 5;
    // 冒泡排序
    for (int i = 0; i < n - 1; i++)
    {
        for (int j = 0; j < n - i - 1; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                // 交换 arr[j] 和 arr[j + 1]
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
    cout << "排序后的数组: ";
    for (int i = 0; i < n; i++)
    {
        cout << arr[i] << " ";
    }
    return 0;
}

24.3 实例讲解 ​

例 1:第 k 小的数 ​

给定一个长度为 n(1 ≤ n ≤ 10000)的序列,问该序列中第 k(1 ≤ k ≤ n)小的元素是多少。

【输入格式】输入两行。第 1 行输入两个整数 n 和 k。

第 2 行输入 n 个数,表示这个序列。

【输出格式】输出一行,为第 k 小的元素。

【输入样例】

5 3
18 23 4 5 12

【输出样例】12

解析

通过冒泡排序对序列进行从小到大排序,排在第几位就是第几小的元素,直接输出这个元素即可。

参考代码

cpp
#include <iostream>
using namespace std;
int s[10001];
int main()
{
    int n, k;
    cin >> n >> k; // n 表示共 n 个数,k 表示输出第 k 个元素
    for (int i = 1; i <= n; i++)
        cin >> s[i];
    for (int i = 1; i <= n - 1; i++)
    {
        for (int j = 1; j <= n - i; j++)
        {
            if (s[j] > s[j + 1]) // 从小到大排序
            {
                int t;
                t = s[j];
                s[j] = s[j + 1];
                s[j + 1] = t;
            }
        }
    }
    cout << s[k]; // 数组下标从 1 开始,所以第 k 小的元素即为 s[k]
    return 0;
}

24.4 冒泡排序优化 ​

在某些序列,比如 [12, 35, 99, 18, 76] 的冒泡排序过程中,我们不难发现,第二轮排序进行完之后,整个序列已经是有序的了,也就是说第二轮排序结束就可以不用进行接下来的比较了。

因此我们可以对刚才的程序进行优化。那么什么时候可以结束排序过程呢?经过观察我们发现,如果某轮排序过程中没有交换发生,那么就说明序列已经有序,无须再次比较了。

优化代码 ​

cpp
#include <iostream>
using namespace std;
int main()
{
    int n, flag = 1; // flag 标记是否有交换
    int arr[200];
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> arr[i];
    for (int i = 1; i <= n - 1; i++)
    {
        if (flag == 0) // 没有交换,直接结束排序
            break;
        flag = 0; // 每轮比较均初始化 flag
        for (int j = 1; j <= n - i; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                flag = 1; // 有交换发生则标记为 1
                int t = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = t;
            }
        }
    }
    for (int i = 1; i <= n; i++)
        cout << arr[i] << " ";
    return 0;
}

例 2:枇杷树 ​

蛙蛙在他的果树林里种了两个品种的枇杷树,第一种高度为 2 米,第二种高度为 3 米。

它们在园地中从低到高排成了一排,现在蛙蛙只知道他一共种了 n 棵枇杷树,但不知道每种枇杷树各有多少棵。

但他有一份树高记录表,每种一棵树他就会在表中做记录。由于枇杷树并不是按照从低到高的顺序种植的,所以表中的记录是无序的。他打算把种植的这一排枇杷树的前 m 棵卖掉,请问第 m 棵枇杷树的高度是 2 还是 3?

【输入格式】输入两行。第 1 行是两个由空格隔开的整数 n、m,表示一共有 n 棵枇杷树(n < 10000),求种的第 m 棵枇杷树的高度。

第 2 行是 n 个由空格隔开的整数,h1, h2, …, hn,分别表示一棵树的高度。

【输出格式】输出一行,为种的树中第 m 棵树的高度。

【输入样例】

8 2
2 3 3 2 2 3 3 2

【输出样例】2

注:原书此处缺少【输出样例】,根据题目解析与样例数据补全为 2。

解析

根据输入的 n 和 m,对第二行输入的 n 个整数进行排序,从排序后的数组中直接取出第 m 个整数,该整数就是第 m 棵枇杷树的高度。

比如在样例中,排序后第 2 棵枇杷树的高度为 2 米,所以输出为 2。

参考代码

cpp
#include <iostream>
using namespace std;
int s[10001];
int main()
{
    int n, m, flag = 1;
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> s[i];
    for (int i = 1; i <= n - 1; i++)
    {
        if (flag == 0)
            break;
        flag = 0;
        for (int j = 1; j <= n - i; j++)
        {
            int t;
            if (s[j] > s[j + 1])
            {
                flag = 1;
                t = s[j + 1];
                s[j + 1] = s[j];
                s[j] = t;
            }
        }
    }
    cout << s[m];
    return 0;
}

基于 MIT 协议发布