C++ 核心知识点汇总(第11日)(排序算法)

#C++ 核心知识点汇总(第11日)(排序算法)

引言

排序是算法领域最基础且核心的问题之一,它不仅是独立的考点,更是许多复杂算法(如二分查找、贪心、动态规划)的前置步骤。


一、排序算法的核心评价指标

在学习具体算法之前,我们需要明确几个关键评价维度,这也是竞赛和面试中高频的考点:

指标 定义 意义
时间复杂度 算法执行的基本操作次数的上界(通常关注最坏情况) 衡量算法的运行效率,是选择算法的核心依据
空间复杂度 算法执行过程中需要的额外内存空间 原地排序(空间复杂度O(1))适合内存受限场景
稳定性 相等元素在排序后相对顺序是否保持不变 处理含附加信息的元素时(如按成绩排序的学生),稳定性至关重要
适应性 对已部分有序的输入是否能自动优化性能 插入排序在输入接近有序时,时间复杂度可降至O(n)

二、基础排序算法(时间复杂度O(n²)

这类算法逻辑简单,易于实现,但效率较低,适合小规模数据(n < 1000)或作为学习算法思想的入门案例。

1. 冒泡排序(Bubble Sort)

核心思想
  • 重复地遍历待排序数组,比较相邻元素,若逆序则交换,使较大的元素像"气泡"一样逐步"上浮"到数组的末尾。
  • 每一轮遍历后,当前未排序区间的最大元素会被放到正确的位置。
代码实现(C++)
cpp 复制代码
void bubbleSort(vector<int>& arr) {
    int n = arr.size();
    for (int i = n - 1; i > 0; --i) {
        bool swapped = false; // 优化:若本轮无交换,说明已排序
        for (int j = 0; j < i; ++j) {
            if (arr[j] > arr[j + 1]) {
                swap(arr[j], arr[j + 1]);
                swapped = true;
            }
        }
        if (!swapped) break;
    }
}
关键特性
  • 时间复杂度O(n²)(最坏、平均),O(n)(最好,输入已完全有序)
  • 空间复杂度O(1)(原地排序)
  • 稳定性:稳定(相等元素不会交换位置)
真题解析

对数组 {5, 3, 8, 1} 执行第一轮冒泡排序:

  1. 比较 53 → 交换 → {3, 5, 8, 1}
  2. 比较 58 → 不交换
  3. 比较 81 → 交换 → {3, 5, 1, 8}
    第一轮结束后最大元素8已沉底,最终数组为:{3, 5, 1, 8}

2. 插入排序(Insertion Sort)

核心思想
  • 将数组分为"已排序"和"未排序"两部分,每次从未排序部分取出第一个元素,插入到已排序部分的合适位置,直到所有元素都被插入。
  • 类似整理扑克牌的过程:将每一张牌插入到前面已整理好的牌堆中。
代码实现(C++)
cpp 复制代码
void insertionSort(vector<int>& arr) {
    int n = arr.size();
    for (int i = 1; i < n; ++i) {
        int base = arr[i]; // 当前待插入的元素
        int j = i - 1;
        // 在已排序区间中找到插入位置
        while (j >= 0 && arr[j] > base) {
            arr[j + 1] = arr[j]; // 元素后移
            j--;
        }
        arr[j + 1] = base; // 插入元素
    }
}
关键特性
  • 时间复杂度O(n²)(最坏、平均),O(n)(最好,输入已完全有序)
  • 空间复杂度O(1)(原地排序)
  • 稳定性:稳定(插入时不会改变相等元素的相对顺序)

3. 选择排序(Selection Sort)

核心思想
  • 每次从未排序区间中找到最小(或最大)的元素,将其与未排序区间的第一个元素交换位置,逐步扩大已排序区间。
代码实现(C++)
cpp 复制代码
void selectionSort(vector<int>& arr) {
    int n = arr.size();
    for (int i = 0; i < n - 1; ++i) {
        int minIdx = i;
        // 找到未排序区间的最小值
        for (int j = i + 1; j < n; ++j) {
            if (arr[j] < arr[minIdx]) {
                minIdx = j;
            }
        }
        swap(arr[i], arr[minIdx]);
    }
}
关键特性
  • 时间复杂度O(n²)(所有情况,因为无论是否有序都要遍历找最小值)
  • 空间复杂度O(1)(原地排序)
  • 稳定性:不稳定(交换操作可能破坏相等元素的相对顺序)

三、高效排序算法(时间复杂度O(n log n)

这类算法是大规模数据排序的首选,通过分治思想或堆结构实现了时间复杂度的突破,也是竞赛和面试的重点。

1. 快速排序(Quick Sort)

核心思想
  • 分治思想:选择一个基准元素(Pivot),将数组划分为两部分,左边元素都小于等于基准,右边元素都大于等于基准,然后递归地对左右两部分进行排序。
代码实现(C++)
cpp 复制代码
// 分区函数:返回基准元素最终的位置
int partition(vector<int>& arr, int low, int high) {
    int pivot = arr[high]; // 选择最后一个元素作为基准
    int i = low - 1; // 小于基准的元素的边界
    for (int j = low; j < high; ++j) {
        if (arr[j] < pivot) { // 遇到小于基准的元素
            i++;
            swap(arr[i], arr[j]);
        }
    }
    swap(arr[i + 1], arr[high]); // 将基准放到正确位置
    return i + 1;
}

void quickSort(vector<int>& arr, int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quickSort(arr, low, pi - 1); // 递归排序左半部分
        quickSort(arr, pi + 1, high); // 递归排序右半部分
    }
}
关键特性
  • 时间复杂度O(n log n)(平均),O(n²)(最坏,如输入已完全有序且选择首尾元素作为基准)
  • 空间复杂度O(log n)(递归栈深度,平均),O(n)(最坏)
  • 稳定性:不稳定(交换操作可能破坏相等元素的相对顺序)
优化技巧
  • 基准选择:避免选择首尾元素,可采用随机选择、三数取中等策略。
  • 尾递归优化:对较长的子数组先递归,减少递归栈深度。
  • 小数组切换 :当子数组长度较小时(如n < 20),切换为插入排序。

2. 归并排序(Merge Sort)

核心思想
  • 分治思想:将数组不断地二分,直到每个子数组只有一个元素(天然有序),然后将两个有序的子数组合并成一个更大的有序数组,最终得到完全有序的数组。
代码实现(C++)
cpp 复制代码
// 合并两个有序数组
void merge(vector<int>& arr, int left, int mid, int right) {
    int n1 = mid - left + 1;
    int n2 = right - mid;
    vector<int> L(n1), R(n2);
    // 复制数据到临时数组
    for (int i = 0; i < n1; ++i) L[i] = arr[left + i];
    for (int j = 0; j < n2; ++j) R[j] = arr[mid + 1 + j];
    // 合并临时数组回原数组
    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            arr[k++] = L[i++];
        } else {
            arr[k++] = R[j++];
        }
    }
    // 复制剩余元素
    while (i < n1) arr[k++] = L[i++];
    while (j < n2) arr[k++] = R[j++];
}

void mergeSort(vector<int>& arr, int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2; // 避免溢出
        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);
        merge(arr, left, mid, right);
    }
}
关键特性
  • 时间复杂度O(n log n)(所有情况,因为二分和合并的时间复杂度都是稳定的)
  • 空间复杂度O(n)(需要额外的临时数组存储合并结果)
  • 稳定性:稳定(合并时遇到相等元素,优先选择左半部分的元素,保持相对顺序)

四、STL 排序工具:std::sort()

在C++开发和竞赛中,我们几乎不需要手动实现排序算法,因为STL提供了高效的std::sort()函数,它是你处理排序问题的首选。

1. 核心特性

  • 底层实现 :通常采用快速排序 的优化版本(如IntroSort),当递归深度过大时自动切换为堆排序,保证最坏时间复杂度为O(n log n)
  • 时间复杂度O(n log n)(平均和最坏)
  • 空间复杂度O(log n)
  • 稳定性:不稳定

2. 基本用法

cpp 复制代码
#include <algorithm>
#include <vector>

int main() {
    vector<int> arr = {5, 2, 9, 1, 5, 6};
    // 默认升序排序
    sort(arr.begin(), arr.end());
    // 降序排序
    sort(arr.begin(), arr.end(), greater<int>());
    // 自定义比较函数(例如:偶数在前,奇数在后)
    sort(arr.begin(), arr.end(), [](int a, int b) {
        return (a % 2 == 0) && (b % 2 != 0);
    });
    return 0;
}

五、常见排序算法对比表

算法 时间复杂度(平均) 时间复杂度(最坏) 空间复杂度 稳定性 适用场景
冒泡排序 O(n²) O(n²) O(1) 稳定 小规模数据、教学演示
插入排序 O(n²) O(n²) O(1) 稳定 小规模数据、输入接近有序
选择排序 O(n²) O(n²) O(1) 不稳定 小规模数据、不要求稳定性
快速排序 O(n log n) O(n²) O(log n) 不稳定 大规模数据、通用场景
归并排序 O(n log n) O(n log n) O(n) 稳定 大规模数据、要求稳定性
STL sort() O(n log n) O(n log n) O(log n) 不稳定 工程开发、算法竞赛首选

六、高频真题解析(必背)

真题1:快速排序分区函数

在快速排序的分区函数中,横线处应填入:

cpp 复制代码
if (arr[j] < pivot) {
    i++;
    swap(arr[i], arr[j]);
}

解析:此逻辑将小于基准的元素交换到左半区,大于等于基准的元素留在右半区,保证分区的正确性。


真题2:归并排序的归并次数

对长度为n的数组,归并排序的merge函数调用次数约为O(n)
解析 :归并排序的每一层递归都会合并n个元素,总共有log n层,因此总合并次数约为n log n / n = log n次,但合并操作的总元素处理量为O(n log n)


真题3:稳定性判断

  • 稳定:冒泡排序、插入排序、归并排序
  • 不稳定:选择排序、快速排序、堆排序

七、总结与学习建议

  1. 基础优先:先掌握冒泡、插入、选择排序的原理和实现,理解时间复杂度、空间复杂度和稳定性的概念。
  2. 重点突破:深入理解快速排序和归并排序的分治思想,这是算法学习的基石,也是面试和竞赛的高频考点。
  3. 实战为王 :在实际开发和竞赛中,优先使用std::sort(),但要理解其底层原理和局限性。
  4. 对比分析:学会根据问题的规模、对稳定性的要求和内存限制,选择最合适的排序算法。
相关推荐
twilight_4693 小时前
机器学习与模式识别——线性回归算法
算法·机器学习·线性回归
玄同7653 小时前
Python Random 模块深度解析:从基础 API 到 AI / 大模型工程化实践
人工智能·笔记·python·学习·算法·语言模型·llm
Pluchon3 小时前
硅基计划4.0 算法 简单模拟实现位图&布隆过滤器
java·大数据·开发语言·数据结构·算法·哈希算法
符哥20083 小时前
C++ 适合初学者的学习笔记整理
c++·笔记·学习
独断万古他化3 小时前
【算法通关】前缀和:和为 K、和被 K整除、连续数组、矩阵区域和全解
算法·前缀和·矩阵·哈希表
历程里程碑3 小时前
普通数组-----除了自身以外数组的乘积
大数据·javascript·python·算法·elasticsearch·搜索引擎·flask
AI视觉网奇3 小时前
blender 导入fbx 黑色骨骼
学习·算法·ue5·blender
星火开发设计3 小时前
this 指针:指向对象自身的隐含指针
开发语言·数据结构·c++·学习·指针·知识
梵刹古音3 小时前
【C++】构造函数
开发语言·c++