InfoCode

C++ · 排序

冒泡、插入、选择排序及稳定性

对比选择排序与插入排序的操作、复杂度和稳定性,保留冒泡排序与 std::sort 示例,并用相同数组逐轮检查结果。

InfoCode 编写 · 技术参考见下文

在应用中练这个知识点 在应用中阅读讲解

冒泡、插入、选择排序与标准库排序

排序就像整理书架:把高矮不一的书按顺序摆好。不同方法有不同效率:

复试中数据量小可用 O(n²) 算法,数据量大必须用 sort。

稳定性:相同关键字的记录排序后仍保持原先相对次序,才叫稳定。只在严格逆序时交换的冒泡排序、只移动严格大于新值元素的插入排序是稳定的;普通交换式选择排序通常不稳定,std::sort 也不保证稳定,需要时使用 stable_sort。

选择排序每轮在未排序部分找最小值,与当前位置交换;插入排序把当前位置暂存,再将已排序部分中比它大的元素右移,最后放入空位。插入排序在已排序输入上可为 O(n),但最坏仍是 O(n²)。

选择排序与插入排序:同一数组逐轮比较

输入为 [3, 1, 2]。选择排序第一轮选出最小值并交换,得到 [1, 3, 2];第二轮得到 [1, 2, 3]。插入排序先把 1 插入已排序前缀,得到 [1, 3, 2],再把 2 插入,得到 [1, 2, 3]。中间数组相同,执行的比较和移动并不相同。

常见实现选择排序插入排序
主要操作在后缀找最小值,再交换向右移动较大元素,再插入
最好时间Θ(n²)Θ(n),输入已排序时
平均/最坏时间Θ(n²)Θ(n²)
额外空间O(1)O(1)
稳定性通常不稳定仅移动严格较大值时稳定

常见选择排序总要做 n(n−1)/2 次键比较,但至多 n−1 次交换;插入排序在已排序输入中只需 n−1 次键比较,逆序时移动次数可达 n(n−1)/2。交换和移动不是同一个计量,不应直接等同。

先自己判断:相等元素也向右移动的插入排序,还能保证稳定吗?

不能。原本靠后的相等元素可能被插到前面;只移动严格较大的元素才能保留原顺序。

排序过程、比较器与稳定性

例1:冒泡排序(理解排序过程)

#include <iostream>
using namespace std;

int main() {
    int a[] = {5, 3, 8, 1, 4};
    int n = 5;

    // 冒泡:n-1 轮,每轮把最大值沉到末尾
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            if (a[j] > a[j + 1]) {
                int tmp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = tmp;
            }
        }
    }

    for (int i = 0; i < n; i++)
        cout << a[i] << " ";
    cout << endl; // 输出 1 3 4 5 8
    return 0;
}

第1轮:5和3交换→3,5,8,1,4;5和8不换;8和1交换→3,5,1,8,4;8和4交换→3,5,1,4,8(8沉底)。经过4轮后得到 1 3 4 5 8。

例2:使用 sort + 自定义比较函数

#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;

int main() {
    int a[] = {5, 3, 8, 1, 4};
    int n = 5;

    // 升序(默认)
    sort(a, a + n);
    for (int i = 0; i < n; i++) cout << a[i] << " ";
    cout << endl; // 1 3 4 5 8

    // 降序:使用  中的 greater 比较器
    sort(a, a + n, greater<int>());
    for (int i = 0; i < n; i++) cout << a[i] << " ";
    cout << endl; // 8 5 4 3 1

    return 0;
}

sort(首地址, 尾地址+1) 对数组原地排序。默认升序;传入 greater<int>() 得降序。竞赛中首选 sort,速度远超冒泡。

例3:插入排序保持相等元素的顺序

#include <iostream>
using namespace std;
int main() {
    int a[] = {3, 1, 2, 1};
    for (int i = 1; i < 4; ++i) {
        int value = a[i], j = i - 1;
        while (j >= 0 && a[j] > value) { a[j + 1] = a[j]; --j; }
        a[j + 1] = value;
    }
    for (int x : a) cout << x << " ";
    cout << endl;
}

输出 1 1 2 3。相等元素不向右移,新的同值项放在已有同值项后面,从而保持稳定。

例4:选择排序的交换可能破坏稳定性

#include <iostream>
#include <utility>
#include <algorithm>
using namespace std;
int main() {
    pair<int,char> a[] = {{2,'A'}, {2,'B'}, {1,'C'}};
    for (int i = 0; i < 3; ++i) {
        int best = i;
        for (int j = i + 1; j < 3; ++j) if (a[j].first < a[best].first) best = j;
        swap(a[i], a[best]);
    }
    for (auto x : a) cout << x.first << x.second << " ";
    cout << endl;
}

输出 1C 2B 2A。首轮把 1C 与 2A 交换后,原本在 2B 前面的 2A 被移到后面,说明不稳定。

提示

先修知识

技术参考

范围说明

本站讲解由 InfoCode 编写,不是官方考纲;参考级别是本站映射,不代表官方等级、认证或参赛资格。练习记录不等于掌握或资格。

参考级别:GESP 4 级(本站映射)

查看赛事关系与路径