STL之stack,queue,heap,优先队列(适配器和仿函数)

本文介绍了栈、队列和堆这三种基本数据结构及其在C++ STL中的实现。栈是一种后进先出(LIFO)的数据结构,常用容器适配器如deque和list实现。队列是一种先进先出(FIFO)的数据结构,deque和list也可作为其底层结构。堆是优先队列的基础,采用二叉堆实现高效插入、删除和查找。此外,文章还讨论了仿函数在STL算法中的应用,以及如何通过适配器和仿函数增强STL组件的功能。
摘要由CSDN通过智能技术生成

1、stack
我们都知道,栈是一种先进后出的结构,我们可以增加元素,删除头部元素,获取头部元素,但是栈无法遍历。

我们都知道deque是双向开口的数据结构,只要把它作为底部结构,并封闭头部端口的相关操作,就可以轻易实现栈。因此栈不称为容器,而是一种容器适配器。
同时,list(双向循环链表)也可以作为栈的底部结构。
因为只有顶部元素可以利用,所以栈是没有迭代器的。源代码也十份简短。

2、queue
队列是一种先进先出的结构,有两个开口,称为顶端和底端,只有底端可以插入元素,顶端可以输出元素,所以队列也是不允许遍历行为。
同样,deque,list都可以作为队列的底部结构。和stack类似。

3.heap
heap不属于STL的容器组件,但是它是优先队列的低层机制。
所谓优先队列,就是元素可以顺序插入,但是取出时返回的是按权值大小排列的。优先队列就是以max heap作为底层机制的。
如果用List作为优先队列的底层,那么元素插入常数时间,但是找到元素极值却需要线性复杂度。还有一种就是用二叉搜索树作为底层,这样插入取得都是LogN复杂度,但是未免小题大做了。而二叉堆(binary heap)就是合适的助手。
所谓的binary heap就是一种完全二叉树(完全二叉树概念自己百度)。因为完全二叉树没有节点漏洞,这就带来一个好处,我们可以用数组array存储所有节点。
我们把数组0号存无限大值作为保留,当完全二叉树的某个节点位于i时,则左子节点位于2i,右子节点位于2i+1,父节点位于i/2.这种用数组表示树的方法,称为隐式表述法。
我们需要的就是一个array和一组算法(用来插入删除取极值等),用vector代替array更好,因为可以动态改变大小。

push_heap算法
两步:把元素放到vector底端;
然后执行一个上溯程序,看看该元素和父节点大小,如果比父节点大,就交换。

pop_heap算法
pop_heap取走根节点,根节点空了,其实是把该节点值移动到vector的末尾,那么我们必须要把末尾的节点移动到根节点。
第二步是执行一个下溯程序,把该节点和较大的子节点交换位置,一直到该节点比两个子节点都大。

sort_heap算法
既然,每次pop_heap都可以取一个最大元素。那么重复执行这个操作(不过每次操作范围从后往前缩减一个范围,因为最大元素放在末尾),这样我们就可以得到一个递增序列了。很好理解。

make_heap算法
用于将现有数据转换成一个heap;
因为heap所有元素都遵循完全二叉树的排列规则,因此heap不提供遍历功能,也没有迭代器。

适配器就是在已有的容器上,修改接口。比如stack,直接template<class T,class Container = std::vector>,然后stack包含一个container变量con,不需要构造函数因为con够用了。那么栈的push,pop,其实就是vector的push_back,pop_back()。
也可以子类继承一下vector,重写接口。

仿函数:实际上是个类,但是重载了()运算符,使得它的对你可以像函数那样子调用(代码的形式好像是在调用函数)。
比如stl排序算法sort中cmp,优先队列的greater,less。

template<typename T> struct comp
{
    bool operator()(T in1, T in2) const
    {
        return (in1>in2);
    }
};

comp<int> m_comp_objext;
cout << m_comp_objext(6, 3) << endl;     //使用对象调用
cout << comp<int>()(1, 2) << endl;       //使用仿函数实现

仿函数的主要作用?用来搭配STL算法
以sort为例,其第一版本以operator<为排序时的元素位置调整依据,第二版本则运行用户指定任何“操作”,务求排序后的两两相邻的元素都能令该操作结果为true
要将某种“操作当做算法的参数,有两种方法:
第一种:先将“操作”设计为一个函数,再将函数指针当做算法的一个参数
第二种:将“操作”设计为一个仿函数(在语言层面是一个class),再以该仿函数产生一个对象,并以此对象作为算法的一个参数
那为什么不用“函数指针”而要引入仿函数呢?因为函数指针不能满足STL对抽象性的要求,也不能满足软件积木的要求:函数指针无法和STL其他组件(比如adapter)搭配,产生更灵活的变化

STL仿函数的分类,如果以操作数的个数划分,可以为:
一元仿函数
二元仿函数
STL不支持三元仿函数
以功能划分:

算术运算
关系运算
逻辑运算

//一元函数的参数和返回值类型,通常被继承
template <class Arg, class Result>
struct unary_function {
    typedef Arg argument_type;
    typedef Result result_type;
};
 
//二元函数的参数和返回值类型,通常被继承
template <class Arg1, class Arg2, class Result>
struct binary_function {
    typedef Arg1 first_argument_type;
    typedef Arg2 second_argument_type;
    typedef Result result_type;
};  

上述两个类分别定义了一元和二元函数参数和返回值型别,对应的仿函数类仅仅需要继承此类即可。标准库给我们定义了一些通用的仿函数,可以直接调用,生成对象,面向用户。

template
struct plus : public binary_function<T, T, T> {
T operator()(const T& x, const T& y) const { return x + y; }
};
仿函数适配器是通过将上述仿函数重新配置成含有新功能的模板函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值