从C++中看stack&queue&priority_queue
1.stack的介绍
官方stack实现: 本质是一个数组1. stack 是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。2. stack 是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部( 即栈顶 ) 被压入和弹出。3. stack 的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:empty :判空操作back :获取尾部元素操作push_back :尾部插入元素操作pop_back :尾部删除元素操作4. 标准容器 vector 、 deque 、 list 均符合这些需求,默认情况下,如果没有为 stack 指定特定的底层容器, 默认情况下使用deque 。
2. queue的介绍
官方queue实现:本质是一个链表1. 队列是一种容器适配器,专门用于在 FIFO 上下文 ( 先进先出 ) 中操作,其中从容器一端插入元素,另一端提取元素。2. 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类, queue 提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。3. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:empty :检测队列是否为空size :返回队列中有效元素的个数front :返回队头元素的引用back :返回队尾元素的引用push_back :在队列尾部入队列pop_front :在队列头部出队列4. 标准容器类 deque 和 list 满足了这些要求。默认情况下,如果没有为 queue 实例化指定容器类,则使用标准容器deque 。
3. priority_queue的介绍
官方实现priority_queue :本质是一个数组,因为被按照特定顺序排列,也又叫做 “堆”。1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。(默认建大堆,降序排列)2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素 ( 优先队列中位于顶部的元素) 。3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类 queue 提供一组特 定的成员函数来访问其元素。元素从特定容器的“ 尾部 ” 弹出,其称为优先队列的顶部。4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭 代器访问,并支持以下操作:empty() :检测容器是否为空size() :返回容器中有效元素个数front() :返回容器中第一个元素的引用push_back() :在容器尾部插入元素pop_back() :删除容器尾部元素5. 标准容器类 vector 和 deque 满足这些需求。默认情况下,如果没有为特定的 priority_queue 类实例化指 定容器类,则使用vector 。6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数 make_heap、 push_heap 和 pop_heap 来自动完成此操作。※注意:优先级队列默认使用 vector 作为其底层存储数据的容器,在 vector 上又使用了堆算法将 vector 中元素构造成 堆的结构,因此 priority_queue 就是堆,所有需要用到堆的位置,都可以考虑使用 priority_queue 。 默认情况下 priority_queue 是大堆(会对数组元素进行降序排列) 。在上方的官方实现当中有:class Container=deque<T> class Container=vector<T>在 C++ 中,一个 Containe 类会使用标准模板库(STL)中的容器类,如deque,来管理元素,来使stack和queue做容器适配器发挥它们的功能。补充:上述场景中stack和queue是通过用deque(class Container=deque<T>)当作容器适配器来实现stack和queue的功能;priority_queue通过用vector( class Container=vector<T>)当作容器适配器来实现priority_queue的功能。代码示例用class Container=deque<T>实现一个stack#pragma oncenamespace xcn {template<class T, class Container = deque<T>>class stack{public:void push(const T& x){_con.push_back(x);}void pop(){_con.pop_back();}const T& top(){return _con.back();}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Container _con;};void test_stack(){stack<int> s;//如果不写 默认使用deque<int>进行适配//stack<int, vector<int>> s;//stack<int, list<int>> s;//stack<int, string> s; s.push(1);s.push(2);s.push(3);while (!s.empty()){cout << s.top() << " ";s.pop();}cout << endl;} }
4.deque的介绍
官方实现deque:兼容 vector和list的功能deque( 双端队列 ) :是一种双开口的 " 连续 " 空间的数据结构 ,双开口的含义是:可以在头尾两端进行插入和 删除操作,且时间复杂度为O(1) ,与 vector 比较,头插效率高,不需要搬移元素;与 list 比较,空间利用率比较高。deque 并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际 deque 类似于一个动态的二维 数组 ,其底层结构如下图所示:+-------+ +-------+ +-------+ +-------+ | Block |-->| Block |-->| Block |-->| Block | +-------+ +-------+ +-------+ +-------+^ ^ ^ ^| | | || | | | +---------------------------------------------+ | 指针数组(Map) | +---------------------------------------------+
- 每个“Block”表示一个内存块,存储deque的一部分元素。其中每个“Block”用迭代器来维护它的空间:cur、first、last、node。
- “Map”是一个指向这些内存块的指针数组。
deque就是可以替代vector和list,但也不是完全替代,也有它的缺陷:
(1). deque 与vector比较:deque 的优势是:头部插入和删除时, 不需要搬移元素,效率特别高 ,而且在 扩容时,也不 需要搬移大量的元素 ,因此其效率是必 vector 高的。(2).deque与list 比较:其底层是连续空间(但并不是真正的连续), 空间利用率比较高 ,不需要存储额外字段。但是, deque 有一个致命缺陷:不适合遍历,因为在遍历时, deque 的迭代器要频繁的去检测其是否移动到 某段小空间的边界,导致效率低下 ,而序列式场景中,可能需要经常遍历,因此 在实际中,需要线性结构 时,大多数情况下优先考虑 vector 和 list , deque 的应用并不多,而 目前能看到的一个应用就是, STL将deque 用其作 为 stack 和 queue 的底层数据结构 。
5.deque补充说明
(1)vector和list的区别:vector是一个可动态增长的 数组
优点:可以进行随机访问,很好的支持排序、二分查找、堆算法等等。缺点:头部或者中间的插入删除效率低(头插头删效率低),并且空间不够了以后增容的代价较大,需要拷贝数据。list是一个带头双向循环的 链表优点:任意位置插入删除数据效率高,时间复杂度:0(1)缺点:不支持随机访问总结: vector和list是两个相辅相成,互补的容器。有没有一种数据结构可以解决vector和list的缺点呢?解决方法:deque容器(见上方)但是也不是说deque容器完全替代了vector和list容器,deque容器也有缺陷:
- 大量的频繁的遍历,效率低。
- 迭代器的遍历相对复杂,效率受到影响。
补充:
容器适配器(stack/queue/ priority_queue)都不支持迭代器遍历,因为他们通常都包含一些特殊性质,如果支持迭代器随便遍历,那他们无法很好的保持他的性质。比如:先进先出,后进先出,这些特有性质。