6 冒泡排序与选择排序

正在加载视频播放器…

GESP C++ 四级:冒泡排序与选择排序精讲

核心目标:理解两种排序算法的原理与执行流程,掌握标准代码实现,理解时间复杂度、交换次数和稳定性,能读懂排序过程并解决综合应用问题。
冒泡排序靠“相邻比较、逐步交换”,每轮把最大元素送到末尾;选择排序靠“标记最小值、最后交换”,每轮把最小元素放到前面。两者时间复杂度都是 O(n²),冒泡稳定、选择不稳定,选择排序的交换次数更少。

一、考纲定位

根据CCF官方考纲,四级知识内容明确包含 “排序概念和稳定性” 与 “经典排序(冒泡排序、插入排序、选择排序等)” 。

具体考核要求包括:

  • 理解排序算法的基本概念与评价指标
  • 掌握冒泡排序、插入排序、选择排序的实现及稳定性分析
  • 能进行简单算法复杂度的估算(含多项式、指数复杂度)
  • 能运用排序算法解决综合应用问题

在四级考试中,排序相关考点通常出现在选择题和程序阅读题中,同时以排序为工具的综合编程题也是高频题型。

二、冒泡排序

2.1 核心思想

冒泡排序的基本流程是:每一轮从头开始两两比较,将较大的项放在较小项的右边,这样每轮下来保证该轮最大的数在最右边。

形象理解:每一轮就像“气泡”从水底往上冒,最大的元素一轮一轮被“冒”到数组末尾。

2.2 执行流程图解

以数组 [5, 3, 8, 1] 为例,升序排列:

第一轮(确定最大值到末尾):

5 3 8 1  →  比较5和3,交换  →  3 5 8 1
3 5 8 1  →  比较5和8,不交换 →  3 5 8 1
3 5 8 1  →  比较8和1,交换  →  3 5 1 8
第一轮结束,8已到末尾

第二轮(确定次大值):

3 5 1 8  →  比较3和5,不交换 →  3 5 1 8
3 5 1 8  →  比较5和1,交换  →  3 1 5 8
5已到倒数第二位

第三轮(确定第三大值):

3 1 5 8  →  比较3和1,交换  →  1 3 5 8

排序完成。

2.3 标准代码实现

for (int i = 0; i < n - 1; i++) {          // 外层:共 n-1 轮
    for (int j = 0; j < n - 1 - i; j++) {  // 内层:每轮比较相邻元素
        if (arr[j] > arr[j + 1]) {         // 前面的更大,交换
            int t = arr[j];
            arr[j] = arr[j + 1];
            arr[j + 1] = t;
        }
    }
}

代码要点:

  • 外层循环 i 控制轮数,共 n-1 轮。
  • 内层循环 j 控制每轮比较次数,第 i 轮比较 n-1-i 次,因为末尾 i 个元素已经排好。
  • arr[j] > arr[j + 1] 是升序排列,若改为 < 则为降序。

2.4 优化:提前终止

如果某一轮没有发生任何交换,说明数组已经有序,可以提前结束。

for (int i = 0; i < n - 1; i++) {
    bool swapped = false;
    for (int j = 0; j < n - 1 - i; j++) {
        if (arr[j] > arr[j + 1]) {
            swap(arr[j], arr[j + 1]);
            swapped = true;
        }
    }
    if (!swapped) break;   // 本轮无交换,数组已有序
}

优化效果:最好情况下(数组已正序),时间复杂度从 O(n²) 降为 O(n)。

三、选择排序

3.1 核心思想

选择排序改进了冒泡排序,将必要的交换次数从 O(n²) 减少到 O(n),但是比较次数仍保持为 O(n²)。每一轮在未排序部分中找到最小元素,然后与未排序部分的第一个元素交换。

形象理解:每轮像“选班长”一样,从剩下的人里选出最小的,放到队伍最前面。

3.2 执行流程图解

以数组 [5, 3, 8, 1] 为例,升序排列:

第一轮(在全部元素中找最小值):

5 3 8 1  →  最小值为1(下标3),与下标0交换  →  1 3 8 5

第二轮(在下标1~3中找最小值):

1 3 8 5  →  最小值为3(下标1),已在正确位置  →  1 3 8 5

第三轮(在下标2~3中找最小值):

1 3 8 5  →  最小值为5(下标3),与下标2交换  →  1 3 5 8

排序完成。

3.3 标准代码实现

for (int i = 0; i < n - 1; i++) {          // 外层:共 n-1 轮
    int minIdx = i;                         // 假设当前位置是最小值
    for (int j = i + 1; j < n; j++) {      // 在未排序部分找最小值
        if (arr[j] < arr[minIdx]) {
            minIdx = j;                     // 记录最小值下标
        }
    }
    if (minIdx != i) {                      // 如果最小值不在当前位置,交换
        swap(arr[i], arr[minIdx]);
    }
}

代码要点:

  • 外层循环 i 表示当前要确定的位置。
  • 内层循环 j 从 i+1 开始,在未排序部分找最小值。
  • 每轮最多只交换一次,交换次数为 O(n)。

四、两种排序对比

4.1 核心区别一览

对比项 冒泡排序 选择排序
核心操作 相邻比较,边比较边交换 先找最小值,最后交换
每轮交换次数 可能多次 最多一次
总交换次数 O(n²) O(n)
比较次数 O(n²)
最好时间复杂度 O(n)(优化后)
最坏时间复杂度 O(n²)
平均时间复杂度
空间复杂度 O(1)
稳定性 稳定 不稳定

4.2 稳定性分析

冒泡排序是稳定的:当 a == b 时,由于只有大于才做交换,故 a 和 b 的位置没有机会交换,所以算法稳定。

选择排序是不稳定的:假设 a == b,且 a 在 b 前面,而某轮循环中最小值在 b 后面,而次最小值需要跟 a 交换,这时候 b 就在 a 前面了。

真题示例:

以下排序算法中,稳定的是( )。
A) 选择排序 B) 冒泡排序 C) 快速排序 D) 堆排序
答案:B

4.3 性能差异的本质

选择排序之所以比冒泡排序“快”,在于交换次数大幅减少。冒泡排序每比较一次就可能交换一次,而选择排序是一轮比较完后才交换一次。当数据量较大、交换操作代价较高时,选择排序的优势更明显。

五、复杂度估算

四级要求掌握简单算法复杂度的估算。

5.1 两种排序的复杂度

复杂度类型 冒泡排序 选择排序
比较次数 O(n²) O(n²)
交换次数 O(n)
最好时间复杂度 O(n)(优化后) O(n²)
最坏/平均时间复杂度 O(n²)
空间复杂度 O(1)

5.2 复杂度估算方法

对于嵌套循环,时间复杂度通常为各层循环次数的乘积。

  • 冒泡排序:外层 n-1 次,内层平均 n/2 次,总计约 (n-1) × n/2,即 O(n²)。
  • 选择排序:外层 n-1 次,内层平均 n/2 次,总计约 O(n²)。

5.3 真题示例

对包含 n 个元素的数组进行冒泡排序,平均时间复杂度一般为( )。
A) O(n) B) O(n log n) C) O(n²) D) O(n³)
答案:C

六、GESP 四级排序真题精讲

真题1:排序(2025年6月)

题目描述:体育课上 n 名同学排成一队,第 i 位同学身高为 hi,体重为 wi。按照“身高更高者优先,身高相同则体重更重者优先”的规则,判断队伍中有多少对同学站错了位置。

解题思路:使用双重循环检查每一对同学,统计逆序对数量。

#include <iostream>
using namespace std;

struct Stu { int h, w; };
Stu p[1005];

int main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> p[i].h >> p[i].w;

    int cnt = 0;
    for (int i = 1; i < n; i++) {
        for (int j = i + 1; j <= n; j++) {
            // 如果第 j 位同学应该排在第 i 位前面,说明站错了
            if (p[j].h > p[i].h || (p[j].h == p[i].h && p[j].w > p[i].w)) {
                cnt++;
            }
        }
    }
    cout << cnt << endl;
    return 0;
}

核心考点:双重循环 + 排序规则判断,本质是统计逆序对数量。

真题2:有序网格(2026年9月)

题目描述:对一个二维网格,先对每一行从左到右升序排序,再对每一列从上到下升序排序,最后输出结果。

关键陷阱:必须先排列,再排第二列,顺序不能反。

// 第一步:对每一行冒泡排序
for (int i = 1; i <= n; i++) {
    for (int last = m; last > 1; --last) {
        for (int j = 1; j < last; ++j) {
            if (a[i][j] > a[i][j + 1]) {
                swap(a[i][j], a[i][j + 1]);
            }
        }
    }
}

// 第二步:对每一列冒泡排序
for (int j = 1; j <= m; ++j) {
    for (int last = n; last > 1; --last) {
        for (int i = 1; i < last; ++i) {
            if (a[i][j] > a[i + 1][j]) {
                swap(a[i][j], a[i + 1][j]);
            }
        }
    }
}

核心考点:二维数组 + 冒泡排序思想 + 行排序与列排序的顺序。

真题3:区间排序(2024年9月)

题目描述:对序列进行多次升序排序,每次选择一个区间 [l, r] 并对区间内所有数字进行升序排序,每次排序在上次结果的基础上进行。

解题思路:每次对指定区间执行冒泡排序即可。

for (int k = 0; k < q; k++) {
    int l, r;
    cin >> l >> r;
    // 对区间 [l, r] 冒泡排序(注意下标从1开始)
    for (int i = l; i < r; i++) {
        for (int j = l; j <= r - (i - l + 1); j++) {
            if (a[j] > a[j + 1]) swap(a[j], a[j + 1]);
        }
    }
}

七、易错点汇总

7.1 冒泡排序易错点

  1. 内层循环边界错误:第 i 轮比较次数应为 n-1-i,写错会导致重复比较或漏比较。
  2. 外层循环边界错误:应为 i < n - 1,不是 i < n。
  3. 交换条件写反:升序用 >,降序用 <,写反则排序方向错误。
  4. 忘记优化:数据已有序时仍执行全部轮次,效率降低。

7.2 选择排序易错点

  1. 内层循环起点错误:应从 i + 1 开始,不是从 0 开始。
  2. 忘记记录下标:找最小值时需要记录下标而非值,否则无法交换。
  3. 每轮交换条件:minIdx != i 时才交换,避免不必要的交换。
  4. 最小值初始化错误:minIdx = i,不是 0。

7.3 通用易错点

  1. 数组下标从0还是从1开始:统一使用一种,不要混用。
  2. 稳定性判断:冒泡稳定,选择不稳定,这是高频选择题考点。
  3. 时间复杂度混淆:两种排序平均和最坏都是 O(n²),但冒泡优化后最好为 O(n)。
  4. 交换次数混淆:选择排序交换次数为 O(n),冒泡为 O(n²)。

八、速查表

需求 冒泡排序 选择排序
核心思想 相邻比较,逐步交换 找最小值,最后交换
代码结构 双层循环,内层相邻比较 双层循环,内层找最小值
每轮交换 可能多次 最多一次
总交换次数 O(n²) O(n)
最好时间复杂度 O(n)(优化后) O(n²)
平均/最坏 O(n²)
稳定性 稳定 不稳定
适用场景 教学、小数据量 交换代价大的场景
升序条件 arr[j] > arr[j+1] arr[j] < arr[minIdx]
降序条件 arr[j] < arr[j+1] arr[j] > arr[maxIdx]

九、总结

冒泡排序与选择排序是GESP四级排序模块的两大核心算法。

  1. 冒泡排序:相邻比较,逐步交换,每轮把最大元素送到末尾。
  2. 选择排序:找最小值,最后交换,每轮把最小元素放到前面。
  3. 两种排序平均和最坏时间复杂度都是 O(n²)。
  4. 冒泡排序最好时间复杂度为 O(n)(优化后),选择排序始终为 O(n²)。
  5. 选择排序的交换次数为 O(n),冒泡为 O(n²),这是选择排序的核心优势。
  6. 冒泡排序稳定,选择排序不稳定,这是高频选择题考点。
  7. 冒泡排序内层边界是 n-1-i,选择排序内层起点是 i+1。
  8. 选择排序找最小值时记录的是下标,不是值。
  9. 二维数组排序时,先排列再排行的顺序不能反。
  10. 多练“排序过程模拟”“稳定性判断”“排序综合应用”三类题。

复习建议:先把两种排序的标准代码写熟,确保闭卷能默写;再练习“阅读程序写结果”类题目,手动模拟排序过程;最后攻克“有序网格”“排序”等综合编程题。考试中先判断题目要求的是排序过程、复杂度、稳定性还是综合应用,再套用对应知识作答。