C/C++编程:STL heap学习

1059 篇文章 285 订阅

概叙

  • heap并不属于STL容器组件,它是个幕后英雄,扮演priority queue的助手。
  • priority queue允许用户以任意次序将任何元素推入容器内,但是取出时一定从优先级最高的元素开始存取。

为什么要用heap作为priority queue的底层机制呢?

  • 如果list作为底层:
    • 如果不排序就拆入,那么插入是O(1)常数级别复杂度,如果要找极值,那么必须遍历链表,复杂度为O(n)
    • 如果排序之后再插入,那么插入时O(n),删除和找极值时O(1)
  • 如果用binary search tree作为底层机制:
    • 元素的插入和查找极值是O(logN)
    • 但是缺点是:binary search tree对输入元素有要求,而且代码比较难以实现
  • binary heap的复杂度就是在listbinary search tree中间

所谓的binary heap就是一种complete binary tree(完全二叉树)。即:

  • 完全二叉树除了最底层的叶节点之外,是填满的
  • 而最底层的叶节点从左到右有不会有空隙

由于完全二叉树整棵树内没有任何节点漏洞,所以我们可以用array来存储所有的节点。

  • 假如这里用一个小技巧,将array的#0元素保留(或者设为无限大值或者无限小值),那么当完全二叉树中某个节点位于array的i处时
    • 其左节点一定在2i
    • 其右节点一定在2i+1
    • 其父节点一定在i/2
  • 这样编程就很容易写了。(STL并没有用这种技巧)

这种以array表示tree的方式,我们叫做隐式表示法

在这里插入图片描述

也就是说,我们只需要一个array和一组heap算法(用来插入元素、删除元素、取极值、将某一组数组成一个heap)。但是array没有办法动态改变大小,而heap却需要这个功能。因此,以vector代替array更好。

根据元素排列方式,heap可以分为两种

  • max-heap:每个节点的key >= 子节点的key,因此其最大值在根节点,总是位于vector的头部
  • min-heap:每个节点的key <= 子节点的key,因此其最小值在根节点,总是位于vector的头部

STL中提供的式max-heap

heap算法

make_heap算法

make_heap用来将一段现有的数据转换为一个heap。

示例

#include <iostream>
#include <list>
#include <stack>
#include <queue>
#include <algorithm>


int main()
{
    int a[9] = {0, 1, 2, 3, 4, 5, 9, 3, 5};
    std::vector<int> vec(a, a+9);
    for(auto i : vec){
        std::cout << i << "\t";
    }
    std::cout << "\n";
    std::make_heap(vec.begin(), vec.end());

    for(auto i : vec){
        std::cout << i << "\t";
    }
    std::cout << "\n";

}

在这里插入图片描述

实现

在这里插入图片描述
在这里插入图片描述

push_heap算法

  • 为了满足完全二叉树的条件,新加入的元素一定要放到最下面一层作为叶子节点,并填补从左到右的第一个空格,也就是把新元素插入到底层vector的end()处
  • 但是新元素不一定适合end()处
    • 如果不满足max-heap的条件(每个节点的键值都大于等于其子节点的键值),那么需要指向[上溯]行为:将新节点与父节点比较,如果其key比父节点大,那么就父子对换位置。一直上溯,直到不需要对换或者到达根节点为止
      在这里插入图片描述

在这里插入图片描述

示例

#include <iostream>
#include <list>
#include <stack>
#include <queue>
#include <algorithm>


int main()
{
    int a[9] = {0, 1, 2, 3, 4, 5, 9, 3, 5};
    std::vector<int> vec(a, a+9);
    std::make_heap(vec.begin(), vec.end());
    for(auto i : vec){
        std::cout << i << "\t";
    }
    std::cout << "\n";

    vec.push_back(7);
    std::make_heap(vec.begin(), vec.end());
    for(auto i : vec){
        std::cout << i << "\t";
    }
    std::cout << "\n";

}

在这里插入图片描述

实现

push_heap算法接受两个迭代器,用来表现一个heap底部容器(vector)的头尾,并且新元素已经插入到底部容器的最尾端如果不符合这两个条件,那么push_heap的指向结果不可预期
在这里插入图片描述

pop_heap算法

  • pop_heap取出堆顶数据,因为是max-heap,那么其堆顶数据为最大值。pop操作拿走根节点之后。为了满足完全二叉树的条件,不能直接取走,否则会出现空洞
    在这里插入图片描述
    我们稍微改变一下思路,如下图。我们把最后一个节点放到堆顶,然后利用同样的父子节点对比方法。对于不满足父子节点大小关系的,互换两个节点,并且重复进行这个过程,直到父子节点之间满足大小关系为止。这就是从上往下的堆化方法

在这里插入图片描述

测试

#include <iostream>
#include <list>
#include <stack>
#include <queue>
#include <algorithm>


int main()
{
    int a[9] = {0, 1, 2, 3, 4, 5, 9, 3, 5};
    std::vector<int> vec(a, a+9);
    std::make_heap(vec.begin(), vec.end() - 1);
    for(auto i : vec){
        std::cout << i << "\t";
    }
    std::cout << "; size = "  <<vec.size() <<"\n";
    
    std::pop_heap(vec.begin(), vec.end());
    for(auto i : vec){
        std::cout << i << "\t";
    }
    std::cout << "; size = "  <<vec.size() <<"\n";

}


在这里插入图片描述

实现

pop_heap算法接受两个迭代器,用来表现一个heap底部容器(vector)的头尾如果不符合这两个条件,那么push_heap的指向结果不可预期
在这里插入图片描述
注意,pop_heap之后,最大元素只是被放置到底部容器的最尾端,尚未被拿走。

  • 如果要取其值,可以用底部容器vector提供的back()操作函数。
  • 如果要移除它,可以用底部容器vector提供的pop_back()操作函数。

sort_heap

原理

既然每次pop_heap可以获得heap中键值最大的元素,如果持续对整个heap做pop_heap操作,每次将操作范围从后往前缩减一个元素(pop_heap会把键值最大的元素放到底部容器的最尾端),当整个持续被指向时,我们就有了一个递增序列

实现

sort_heap算法接受两个迭代器,用来表现一个heap底部容器(vector)的头尾如果不符合这两个条件,那么sort_heap的指向结果不可预期。注意,排序之后,原来的heap就不是一个合法的heap了
在这里插入图片描述

示例

#include <iostream>
#include <list>
#include <stack>
#include <queue>
#include <algorithm>


int main()
{
    int a[9] = {0, 1, 2, 3, 4, 5, 9, 3, 5};
    std::vector<int> vec(a, a+9);
    std::make_heap(vec.begin(), vec.end() - 1);
    for(auto i : vec){
        std::cout << i << "\t";
    }
    std::cout << "; size = "  <<vec.size() <<"\n";

    std::sort_heap(vec.begin(), vec.end());
    for(auto i : vec){
        std::cout << i << "\t";
    }
    std::cout << "; size = "  <<vec.size() <<"\n";

}

在这里插入图片描述

heap没有迭代器

heap的所有元素都必须遵循特别的(complete binary tree)的排列规则,所以heap不提供遍历功能,也不提供迭代器

指定array作为底层容器

#include <iostream>
#include <list>
#include <stack>
#include <queue>
#include <algorithm>


int main()
{
    int a[9] = {0, 1, 2, 3, 4, 5, 9, 3, 5};
    std::make_heap(a, a + 9);
    for(auto i : a){
        std::cout << i << "\t";
    }
    std::cout << ";  "   <<"\n";

    std::sort_heap(a, a + 9);
    for(auto i : a){
        std::cout << i << "\t";
    }
    std::cout << ";  "   <<"\n";

    std::make_heap(a, a + 9);
    for(auto i : a){
        std::cout << i << "\t";
    }
    std::cout << ";  "   <<"\n";

    std::pop_heap(a, a + 9);
    std::cout << a[8] << ";  "   <<"\n";
}

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值