洛谷P1168题终极解析:双堆法高效计算动态中位数 | 数据结构实战教程

贾蔷
• 阅读 2

洛谷P1168题终极解析:双堆法高效计算动态中位数 | 数据结构实战教程 一、问题理解与算法思路 题目要求我们动态维护一个序列,并在每次读取奇数个数字时输出当前序列的中位数。这道题考察了两个核心算法点:堆数据结构的应用和中位数的高效计算。我们采用双堆法(一个大根堆和一个小根堆)来高效解决这个问题。

‌解题关键步骤‌:

使用大根堆存储较小的一半数 使用小根堆存储较大的一半数 保持两个堆的大小平衡 在奇数位置时输出大根堆的堆顶元素 二、完整代码实现(带详细注释)

#include <iostream>
#include <vector>
#include <queue>
using namespACe std;

int main() {
    ios::sync_with_stdio(false);  // 关闭同步,提高输入输出速度
    cin.tie(nullptr);             // 解除cin和cout的绑定

    int N;
    cin >> N;
    vector<int> A(N);  // 存储输入序列
    for (int i = 0; i < N; ++i) {
        cin >> A[i];    // 读取输入数据
    }

    // 大根堆存储较小的一半数
    priority_queue<int> max_heap;
    // 小根堆存储较大的一半数
    priority_queue<int, vector<int>, greater<int>> min_heap;

    for (int i = 0; i < N; ++i) {
        // 将新元素插入到合适的堆中
        if (max_heap.empty() || A[i] <= max_heap.top()) {
            max_heap.push(A[i]);  // 小于等于大根堆顶元素,放入大根堆
        } else {
            min_heap.push(A[i]);  // 否则放入小根堆
        }

        // 平衡两个堆的大小,保持max_heap比min_heap多0或1个元素
        if (max_heap.size() > min_heap.size() + 1) {
            min_heap.push(max_heap.top());  // 大根堆过大,移动元素到小根堆
            max_heap.pop();
        } else if (min_heap.size() > max_heap.size()) {
            max_heap.push(min_heap.top());  // 小根堆过大,移动元素到大根堆
            min_heap.pop();
        }

        // 输出前奇数项的中位数
        if ((i + 1) % 2 == 1) {
            cout << max_heap.top() << "\n";  // 中位数即大根堆顶元素
        }
    }

    return 0;
}

三、算法核心解析 ‌双堆结构‌:大根堆存储较小的一半数,小根堆存储较大的一半数 ‌元素插入策略‌:新元素根据与堆顶元素比较决定放入哪个堆 ‌堆平衡维护‌:保证大根堆最多比小根堆多一个元素 ‌中位数获取‌:奇数位置时,大根堆顶即为当前中位数 四、复杂度分析与优化 ‌时间复杂度‌:O(N log N),每个元素插入堆操作耗时O(log N) ‌空间复杂度‌:O(N),需要存储所有元素 ‌优化建议‌: 可以使用更高效的堆实现 对于特定数据分布可以考虑其他算法 五、常见问题解答 Q:为什么使用两个堆而不是排序? A:排序的时间复杂度是O(N^2 log N),而双堆法可以达到O(N log N)。

Q:如何处理偶数个元素的情况? A:题目只要求输出奇数位置时的中位数,所以不需要处理偶数情况。

Q:算法是否可以扩展到求任意百分位数? A:可以,通过调整两个堆的大小比例可以实现。

参考:洛谷P1168题终极解析:双堆法高效计算动态中位数 | 数据结构实战教程

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
java堆排序(大根堆)
实现堆排序的算法思路是先创建堆,也就是从叶子节点起对每一层的孩子节点及其对应位置的父亲节点进行比较,较大的孩子节点替换较小的父亲节点,一级一级比较替换,就创建出了大根堆,小根堆反之。创建好大根堆以后,我们,将整棵树的根节点与最后最后一个节点替换位置,然后去除最后一个节点,在创建一个新的大根堆,以此类推,完成排序。代码如下:/\\\<p堆排
可莉 可莉
3年前
10亿个数中找出最大的10000个数(top K问题)
这个问题还是建立最小堆比较好一些。    先拿10000个数建堆,然后一次添加剩余元素,如果大于堆顶的数(10000中最小的),将这个数替换堆顶,并调整结构使之仍然是一个最小堆,这样,遍历完后,堆中的10000个数就是所需的最大的10000个。建堆时间复杂度是O(mlogm),算法(https://www.oschina.net/action
贾蔷 贾蔷
2星期前
蓝桥杯2023接龙数列(洛谷P9242)题解:动态规划与数字首尾匹配的完美应用
一、题目解读这道蓝桥杯省赛真题要求找出数字序列中最长的接龙子序列(每个数字的首位等于前一个数字的末位),并计算需要删除的最少数字个数。题目考察动态规划的实际应用能力,是理解数字特征处理和状态转移的典型案例。二、解题步骤1.处理n1的特殊边界情况2.读取输入
贾蔷 贾蔷
4小时前
牛客12576题全解析:动态规划+质因数分解解决跳跃问题
一、题目解读牛客12576题是一道经典的算法题,要求给定起点N和终点M,求解从N到M的最少跳跃次数。题目考察的核心在于路径优化与动态规划思想,需结合数论中的质因数分解技巧,通过合理设计算法降低时间复杂度,避免暴力枚举的指数级耗时。二、解题思路采用“动态规划
深度学习 深度学习
4小时前
洛谷1111题解:基于Kruskal算法与并查集的最小生成树实现
一、题目解读洛谷1111题是一道经典的图论问题,要求构建一个无向图的最小生成树,并输出其最大边权值。题目核心在于通过给定的边集合,找到连接所有节点的最小权值子集,同时保证无环。这通常涉及最小生成树算法(如Kruskal)的应用,需要高效处理边权重与节点连通
贾蔷 贾蔷
4小时前
牛客13279题解:利用递归与深度优先搜索计算树的最大高度(附完整代码)
一、题目解读牛客13279题要求计算给定树的最大高度。题目输入一棵以邻接表形式表示的树(节点从0开始编号),需要输出从根节点到最深叶节点的最长路径长度。树的结构由n个节点和n1条边构成,保证为连通无环图。理解题目核心在于准确获取树的拓扑关系,并设计算法遍历
贾蔷 贾蔷
4小时前
2023年GESP六级题解:洛谷P10108闯关游戏动态规划解法详解
一、题目解读本文针对2023年GESP六级题目“闯关游戏”(洛谷P10108)进行详细解析。题目要求玩家通过不同关卡路径选择,计算从起点到终点的最大得分。关卡间存在跳跃规则,需结合动态规划思想设计高效算法,最终输出最优得分。二、解题思路采用动态规划(Dyn
深度学习 深度学习
4小时前
洛谷P2034题解:动态规划+单调队列优化求解最大K段子段和问题
一、题目解读洛谷P2034题目要求给定一个长度为n的整数数组,将其分成不超过k段,求各段和的最大值。该问题属于经典动态规划问题的扩展,需结合优化技巧高效求解。二、解题思路采用动态规划单调队列优化的策略。核心思想是定义状态dp
贾蔷 贾蔷
1个月前
力扣145题:二叉树的后序遍历, 解题思路与C++实现
题目介绍力扣第145题要求实现一个函数,该函数接收一个二叉树的根节点,并返回该树的后序遍历结果。后序遍历是一种遍历二叉树的算法,其顺序为:先遍历左子树,是右子树,是根节点。解题思路分析解题时,我们可以使用递归或迭代的方法。递归方法较为直观,但可能导致栈溢出