[特殊字符]征服蓝桥杯:排序算法核心考点深度剖析----附逆序对、第k大元素等高频题代码模板

           文章目录


前言:为什么排序算法是蓝桥杯的核心考点?

在蓝桥杯竞赛中,排序算法不仅是直接考查点(如求第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 < ja[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:特殊排序(自定义规则)

题目要求:将偶数移到奇数后,且偶数相对顺序不变
解题思路

  1. 自定义比较规则:偶数优先级低于奇数

  2. 使用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);  

四、竞赛技巧与资源推荐

  1. STL的妙用

    // 求第k大元素(适用于非极端数据)  
    nth_element(arr.begin(), arr.begin()+k-1, arr.end(), greater<int>());  

  2. 刷题平台

    • 蓝桥杯题库(历年真题)

    • LeetCode:215.数组中的第K个最大元素、剑指Offer 51.逆序对


结语:从排序到算法思维的跨越

排序算法是竞赛中最基础的“武器库”,掌握其核心思想后,可将其应用于:

  • 贪心算法:通过排序确定最优选择顺序

  • 双指针:在有序数组上高效查找

  • 动态规划:预处理数据加速状态转移

行动建议

  1. 手写每个算法至少3遍,理解每行代码的意义

  2. 用本文模板刷10道相关题目


“代码成就万世基,积沙镇海;梦想永在凌云意,意气风发!” —— 祝各位选手在蓝桥杯赛场锋芒毕露! 🏆

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值