0
点赞
收藏
分享

微信扫一扫

【日志】二叉堆

奋斗De奶爸 2022-02-28 阅读 88
数据结构

二叉堆

二叉堆是一棵完全二叉树,其结点满足特定条件。(比如小根堆(或者小顶堆)中,父亲结点的权值不大于儿子节点)。

c++的stl里面的priority_queue的底层实现数据结构即为二叉堆,但是平板电视里面的不是。

下文都是基于数组实现的二叉堆,且根节点为1,也是以小根堆作为模板实现的。

操作

基础操作

下面这两个操作的目的在于维护堆的性质。

向上调整

对单个结点进行调整。

当前结点如果小于其父亲结点,就需要将其与父亲节点交换,直到不能替换为止。

时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn),很好得出(二叉树的层数)。

inline void pushup(int pos) {
    while (pos > 1 && heap[pos >> 1] < heap[pos]) {
        swap(heap[pos >> 1], heap[pos]);
        pos >>= 1;
    }
}

向下调整

也是对单个结点进行调整。

当前结点如果比其儿子大时,就需要将其交换。

inline void pushdown(int pos) {
    while ((pos << 1) <= n) {
        int cl = pos << 1;
        if ((t | 1) <= n && heap[t | 1] < heap[t]) {
            t |= 1;
        }
        if (heap[pos] < heap[t]) {
            break;
        }
        swap(heap[pos]. heap[t]);
        pos = t;
    }
}

时间复杂度也是 O ( log ⁡ n ) O(\log n) O(logn)

建堆

暴力插入法

对于每个要插入的元素,直接在堆的最后面加入,同时向上调整。

时间复杂度大致需要 O ( n log ⁡ n ) O(n \log n) O(nlogn)的级别。

逐个向上调整

对于一个给定的序列,直接按顺序向上调整,像下面的代码一样。

void makeheap(int len) {
    for (int i = 1; i <= len; ++ i) {
        up(i);
    }
}

从第一个元素开始,到最后一个元素,时间复杂度按向上调整累加,也就是:
log ⁡ 2 + log ⁡ 3 + log ⁡ 4 + ⋯ + log ⁡ n \log 2 + \log 3 + \log 4 + \cdots + \log n log2+log3+log4++logn
至少也比暴力插入好点。

逐个向下调整

如果用上面思路,使用向下调整呢?就需要像下面一样。

void makeheap(int len) {
    for (int i = len; i >= 1; -- i) {
        down(i);
    }
}

时间复杂度大概也是一样的。

为什么一个是正序另一个是倒序

这两种建堆方式的原理其实还是对序列进行调整,也就是到目前这个结点时,堆的性质被破坏了,需要调整这个结点以维护堆的性质

向上调整需要正序,也就是默认前面序列已经是符合堆的性质了。向下调整同理。

删除

删除操作比较简单,直接把堆顶赋值为堆序列的最后一个元素,然后对堆顶做一次向下调整即可。

STL里关于堆的东西

priority_queue

使用priority_queue前必须要#inlcude <queue>

priority_queue默认的比较函数为less<>(),从而使其为一个大根堆。若要使用小根堆,需要使用greater<>()

函数

make_heap

make_heap函数可以将给定范围的可迭代容器变成一个堆,默认比较函数为less<>()

使用例:

void solve() {
    vector<int>v(n + 1);
    ...
    make_heap(v.begin(), v.end(), greater<int>());
}

pop_heap

将第一个元素与最后一个元素交换,然后再对前面 n − 1 n-1 n1个元素进行make_heap

原先弹出的元素仍位于最后一个元素

push_heap

把数据插入堆中,用法和make_heap大致。

使用雷点

要插入的数据一定要在调用这个函数之前就已经存在于最后一个位置了

sort_heap

这个并非堆排序,而是对堆内元素进行排序。

在使用这个函数之前,要确保原先给定范围是否是一个合法的堆

堆的应用

堆排序

可以使用堆来进行排序,时间复杂度为 O ( log ⁡ n ) O( \log n) O(logn)

优先队列

一般也是用priority_queue实现(自己打代码速度太慢了,写这一篇花了大概20分钟以上)。

比较经典的用法就是使用了优先队列优化的Dijkstra算法。

对顶堆

也就是直接使用两个堆进行维护,比如一个小根堆和一个大根堆来维护第 k k k大的值。就像这道题一样[P1801 黑匣子](P1801 黑匣子 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))。

举报

相关推荐

0 条评论