文章目录
前言:为什么排序算法是蓝桥杯的核心考点?
在蓝桥杯竞赛中,排序算法不仅是直接考查点(如求第k大元素、逆序对数量),更是解决贪心、动态规划等题型的底层工具。据统计,近5年C++赛道中约30%的题目需用到排序或变种思想。本文将深入剖析高频排序算法及其竞赛应用,提供可直接套用的代码模板,助你快速突破算法难关!
一、排序算法核心知识图谱
1. 算法分类与性能对比
| 算法 | 平均时间复杂度 | 是否稳定 | 适用场景 |
|---|---|---|---|
| 选择排序 | O(n²) | 不稳定 | 数据量小对空间有严格要求 |
| 插入排序 | O(n²) | 稳定 | 数据量小或中等规模 |
| 冒泡排序 | O(n²) | 稳定 | 数据量小或几乎有序 |
| 快速排序 | O(n log n) | 不稳定 | 通用场景,需避免最坏情况 |
| 归并排序 | O(n log n) | 稳定 | 链表排序、逆序对统计 |
竞赛技巧:
-
优先掌握快速排序(分治思想)和归并排序(逆序对问题)
-
遇到“保持原有顺序”要求时选择稳定排序(如归并排序)
2. 代码实现
快速排序:
// 快速排序函数
void quickSort(vector<int>& arr, int low, int high) {
if (low < high) { // 如果区间内有多个元素
// 执行分区操作,返回分区点索引
int pivotIndex = partition(arr, low, high);
// 对分区点左侧的子数组进行递归排序
quickSort(arr, low, pivotIndex - 1);
// 对分区点右侧的子数组进行递归排序
quickSort(arr, pivotIndex + 1, high);
}
}
// 分区函数,将数组分为两部分,小于等于pivot的在左边,大于pivot的在右边
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; // 返回分区点的索引
}
归并排序:
// 归并两个有序子数组 arr[left...mid] 和 arr[mid+1...right]
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];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
// 复制剩余的元素(如果有的话)
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
}
// 主归并排序函数
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);
}
}
二、高频考点实战解析
1. 逆序对问题(归并排序变种)
题目特征:求满足i < j且a[i] > a[j]的二元组数量
代码模板:
// 合并并统计逆序对
long long mergeAndCount(vector<int>& arr, int l, int mid, int r) {
vector<int> temp(r - l + 1); // 临时数组存放合并结果
int i = l, j = mid + 1, k = 0;
long long count = 0;
while (i <= mid && j <= r) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
// 当左半部分元素 > 右半部分元素时,产生逆序对
temp[k++] = arr[j++];
count += (mid - i + 1); // 关键统计步骤!
}
}
// 处理剩余元素
while (i <= mid) temp[k++] = arr[i++];
while (j <= r) temp[k++] = arr[j++];
// 将排序结果拷贝回原数组
for (int m = 0; m < k; m++) {
arr[l + m] = temp[m];
}
return count;
}
// 归并排序递归主体
long long mergeSortAndCount(vector<int>& arr, int l, int r) {
long long cnt = 0;
if (l < r) {
int mid = l + (r - l) / 2;
cnt += mergeSortAndCount(arr, l, mid); // 左半部分逆序对
cnt += mergeSortAndCount(arr, mid+1, r); // 右半部分逆序对
cnt += mergeAndCount(arr, l, mid, r); // 合并时新增的逆序对
}
return cnt;
}
真题应用:
-
蓝桥杯2018年省赛“最小交换次数”:逆序对数量即答案
2. 第k大元素(快速选择算法)
题目特征:要求时间复杂度O(n)且无需完全排序
代码模板:
// 随机分区函数(避免最坏时间复杂度)
int partition(vector<int>& arr, int left, int right) {
// 随机选择基准元素并交换到末尾
int random = left + rand() % (right - left + 1);
swap(arr[random], arr[right]);
int pivot = arr[right];
int i = left - 1; // 指向最后一个小于pivot的元素
for (int j = left; j < right; j++) {
if (arr[j] < pivot) {
i++;
swap(arr[i], arr[j]);
}
}
swap(arr[i+1], arr[right]); // 将基准放到正确位置
return i + 1; // 返回基准的最终位置
}
// 快速选择核心函数(找第k大元素)
int quickSelect(vector<int>& arr, int left, int right, int k) {
if (left == right) return arr[left]; // 只剩一个元素时直接返回
int pos = partition(arr, left, right);
int current_rank = pos - left + 1; // 当前元素是[left..right]中的第current_rank小
if (k == current_rank) {
return arr[pos];
} else if (k < current_rank) {
return quickSelect(arr, left, pos - 1, k);
} else {
return quickSelect(arr, pos + 1, right, k - current_rank);
}
}
// 寻找第k大元素
int findKthLargest(vector<int>& nums, int k) {
srand(time(NULL)); // 初始化随机种子
int n = nums.size();
// 将问题转换为寻找第(n - k + 1)小的元素(注意k从1开始计数)
return quickSelect(nums, 0, n-1, n - k + 1);
}
避坑指南:
-
始终用随机化基准(避免有序数据导致O(n²))
-
第k大元素需转换为第(n-k+1)小元素
三、蓝桥杯真题扩展训练
真题1:特殊排序(自定义规则)
题目要求:将偶数移到奇数后,且偶数相对顺序不变
解题思路:
-
自定义比较规则:偶数优先级低于奇数
-
使用stable_sort保持稳定性
bool cmp(int a, int b) {
if (a%2 == b%2) return false;
return a%2 < b%2; // 偶数在后
}
stable_sort(arr.begin(), arr.end(), cmp);
真题2:多关键字排序(结构体排序)
题目要求:学生按分数降序,同分按姓名升序排列
代码实现:
struct Student {
string name;
int score;
};
bool compare(const Student& a, const Student& b) {
if (a.score != b.score)
return a.score > b.score;
else
return a.name < b.name;
}
sort(students.begin(), students.end(), compare);
四、竞赛技巧与资源推荐
-
STL的妙用:
// 求第k大元素(适用于非极端数据) nth_element(arr.begin(), arr.begin()+k-1, arr.end(), greater<int>()); -
刷题平台:
-
蓝桥杯题库(历年真题)
-
LeetCode:215.数组中的第K个最大元素、剑指Offer 51.逆序对
-
结语:从排序到算法思维的跨越
排序算法是竞赛中最基础的“武器库”,掌握其核心思想后,可将其应用于:
-
贪心算法:通过排序确定最优选择顺序
-
双指针:在有序数组上高效查找
-
动态规划:预处理数据加速状态转移
行动建议:
-
手写每个算法至少3遍,理解每行代码的意义
-
用本文模板刷10道相关题目
“代码成就万世基,积沙镇海;梦想永在凌云意,意气风发!” —— 祝各位选手在蓝桥杯赛场锋芒毕露! 🏆

被折叠的 条评论
为什么被折叠?



