之前我们学习了STL的几个容器: string, vector, list. 今天我们来看容器适配器: stack(栈), queue(队列), priority_queue(优先级队列). 这次我们谈谈适配器的思想和 stack的使用和实现. 那么话不多说, 直入主题, 耐心看完, 你一定有所收获~
一. 适配器的理解
适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。
打个比方就是充电器, 给手机充电肯定不能直接连家里220V的电源吧…这时候就需要适配器, 把220V的电压转换成需要的电压值, 供给人们使用
拿栈stack来举个例子, 只要能满足栈的操作的容器都可以用来实现栈, 比如vector, list, deque都可以实现stack, 我们拿vector来说, vector提供了很多的操作, 但是stack实现的操作很少, 那么我只需要将vector封装成stack, 把stack需要的操作暴露出来就可以了, 其他操作不暴露出来. 这样就完成了一个容器适配器.
二. deque的简单认识
deque(双端队列):是一种双开口的"连续"空间的数据结构,
双开口的含义是:可以在头尾两端进行插入和 删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。
要注意的是 : 双端队列deque是原生实现的容器, 但他一般都作为其他容器适配器的底层容器, 而不作为容器单独使用
下面我们来简单了解一下deque 的结构, 这里不做过深的解释, 原理比较复杂, 感兴趣的小伙伴可以去查阅相关资料…
deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组
其底层结构如下图所示:
双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落 在了deque的迭代器身上
因此deque的迭代器设计就比较复杂,如下图所示:
具体的关系如下图所示:
简单总结一下:
deque可以实现头插, 头删, 尾插, 尾删
它并不是真正的连续空间, 而是一个指针数组, 每个指针指向一个连续空间
deque从中间开始存放数据, 当前缓冲区装满之后新开辟空间, 新开辟空间时, 头插在末尾, 尾插在头部, 保证数据的逻辑连续性
deque的优缺点:
与vector比较,deque的优势是:
头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。
与list比较,
其底层是连续空间,空间利用率比较高,不需要存储额外字段。
而他的缺点也很明显
不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到 某段小空间的边界,导致效率低下
而序列式场景中,可能需要经常遍历,
因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list
deque的应用并不多,而目前能看到的一个应用就是,STL用其作 为stack和queue的底层数据结构
我们说deque是一种比较中庸的容器, 因为他的随机访问效率低于vector, 插入效率低于list. 这也是deque容器的结构所致的, 有得有失.
没有最好的容器, 只有最合适的场景
下面我们来看看为什么stack和queue使用deque作为默认底层实现容器呢 ?
stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性结构,都可 以作为stack的底层容器,比如vector和list都可以;
queue是先进先出的特殊线性数据结构,只要具有 push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。(vector不能实现queue, 因为vector不提供pop_front操作)
但是STL中对stack和 queue默认选择deque作为其底层容器,主要是因为:
- stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。
- 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且deque是连续空间, 内存使用率高。
结合了deque的优点,而完美的避开了其缺陷。
OK, 容器适配器和deque就说到这里, 下次我们就看看几种容器适配器的使用和实现.