一.什么是stack
- stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行
- 元素的插入与提取操作。
- stack是作为容器适配器被实现的,容器适配器是使用特定容器类的封装对象作为其基础容器的类,提供一组特定的成员函数来访问其元素。元素从特定容器的“背面”(称为堆栈顶部)推/弹出。
- stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下
- 操作:
- empty:判空操作
- back:获取尾部元素操作
- push_back:尾部插入元素操作
- pop_back:尾部删除元素操作
- 标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque。

二.stack的使用
| 函数说明 | 接口说明 |
| stack() | 构造空的栈 |
| empty() | 检测stack是否为空 |
| size() | 返回stack中的元素个数 |
| top() | 返回栈顶元素的引用 |
| push() | 将元素val压入stack中 |
| pop() | 将stack中尾部元素弹出 |
| emplace() | 将元素插入到栈顶 |
1.emplace
代码示例
#include <iostream> #include <stack> #include <string> int main() { std::stack<std::string> mystack; mystack.emplace("hello"); mystack.emplace("world"); while (!mystack.empty()) { std::cout << mystack.top() << " "; mystack.pop(); } return 0; } //world hello
stack的模拟实现
#pragma once #include <list> #include<vector> #include<deque> #include<iostream> namespace happy { //stack<int,vector> //stack<int,list> template <class T,class Container= std::deque<T>> class stack { public: void push(const T& x) { s.push_back(x); } void pop() { s.pop_back(); } const T& top() { return s.back(); } bool empty() { return s.empty(); } size_t size() { return s.size(); } private: Container s; }; }
三.什么是deque
deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。

注意:deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示 :

结构图解释:
deque实际上是一段一段空间拼接而成,用完了开的这一片空间之后才会去开另一块空间,并且这些空间由中控数组(指针数组)统一管理,当然如果中控数组中的空间用完的话也需要重新开一个中控数组。我将它形容为缝合怪,链表中含有数组,deque开空间不像链表一样每插入一个数据就需要开一个空间,可以理解为deque开了一个vector,该vector用完之后才会去开另一个数组,避免了频繁开空间。
deque相关操作解释
①insert
双端队列旨在高效地从序列的末尾或开头执行插入。在其他位置的插入通常不如在list或forward_list容器中有效。其原因是若要进行头插操作,直接开一块空间,在该空间尾部插入数据,若要进行尾插(插入空间已满)则开一块空间,在该空间头部插入数据。在中间进行插入则需要一块一块挪动空间,效率低下。
代码示例:
int main() { std::deque<int> dq1; // 在尾部插入元素 dq1.push_back(1); // 在头部插入元素 dq1.push_front(0); // 在指定位置插入元素 dq1.insert(intDeque.begin() + 1, 2); // 使用迭代器范围插入元素 std::deque<int> temp = {3, 4}; dq1.insert(dq1.end(), temp.begin(), temp.end()); return 0; }//0 2 1 3 4②erase
从双端队列容器中删除单个元素(位置)或一系列元素([first,last)。这有效地减少了被移除的元素数量,从而减小了容器的大小。双端队列旨在高效地在序列的末尾或开头删除元素。其他位置上的删除通常不如list或forward_list容器中的效率高。其原因是,若要在头部进行删除只需要将first指针往后挪动,若要在尾部删除,只需要将last指针往前挪动,避免了数据的挪动覆盖,但是在中间进行删除的时候仍然需要数据挪动覆盖,效率自然较低。
代码示例:
std::deque<int> dq1 = {0, 1, 2, 3, 4}; // 删除尾部元素 dq1.pop_back(); // 删除头部元素 dq1.pop_front(); // 删除指定位置的元素 dq1.erase(dq1.begin() + 1); // 使用迭代器范围删除元素 dq1e.erase(dq1.begin() + 1, dq1.end() - 1);③访问方式
int main() { std::deque<int> dq1 = {0, 1, 2, 3, 4}; // 使用下标访问元素 std::cout << "Element at index 2: " << dq1[2] << std::endl;//2 // 使用at函数访问元素(更安全,会检查索引是否越界) std::cout << "Element at index 3: " << dq1.at(3) << std::endl;//3 // 使用迭代器访问元素 for (std::deque<int>::iterator it = dq1.begin(); it != dq1.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl; // 使用范围for循环访问元素 for (int val : dq1) { std::cout << val << " "; } std::cout << std::endl; return 0; }其中operator[]访问方式如下:
第几个数组:i/buffsize 目标数组中第几个元素:i%buffsize
疑问:为什么插入删除时不像数组一样修改大小?
原因:那么每一个数组的buffsize就会不一样,就没有办法实现下标的随机访问。
deque的特点
deque内存管理
deque的内存管理是其与vector的主要区别之一。deque由多个连续的内存块组成,这些内存块之间通过指针或引用相互连接。当在deque的两端插入或删除元素时,只需要调整相应内存块的指针或引用,而不需要像vector那样重新分配整个内存块并复制元素。这使得deque在两端进行插入和删除操作时效率更高。
deque与list和vector的比较
list:
deque 底层是连续空间,空间利用率比较高,不需要额外存储字段。
vector
在头部插入和删除时,不需要挪动数据,效率比较高,而且在扩容的时候,不需要搬移大量数据。
缺陷:
deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历。因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。
为什么选择deque作为vector和list的底层默认容器
stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性结构,都可以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。
但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:
- stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作,而deque头插头删、尾插尾删效率很高.
- 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高(与list相比)。
结合了deque的优点,而完美的避开了其缺陷。
四.什么是queue
- 队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。
- 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。
- 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:
- empty:检测队列是否为空
- size:返回队列中有效元素的个数
- front:返回队头元素的引用
- back:返回队尾元素的引用
- push_back:在队列尾部入队列
- pop_front:在队列头部出队列
标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。

| 函数声明 | 接口说明 |
| queue() | 构造空的队列 |
| empty() | 检测队列是否为空,是则返回true,否则返回false |
| size() | 返回队列中有效元素个数 |
| front() | 返回队头元素的引用 |
| back() | 返回队尾元素的引用 |
| push() | 在队尾将元素val入队列 |
| pop() | 将队头元素出队列 |
queue的模拟实现
#pragma once #include <list> #include<vector> #include<deque> #include<iostream> namespace happy { template<class T,class container=std::deque<T>> class queue { public: void push(const T& x) { _con.push_back(x); } void pop() { _con.pop_front(); } size_t size() { return _con.size(); } const T& top() { return _con.front(); } bool empty() { return _con.empty(); } private: container _con; }; }
五.什么是priority_queue
- 优先级队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含中最大的。
- 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆的元素(优先队列中位于顶部的元素)。
- 优先队列被实现为容器适配器,容器适配器将特定容器类封装作为底层容器类,queue提供一组特殊的成员函数来访问其成员,元素从特定容器”尾部“弹出,其称为优先级队列的顶部。
- 底层容器可以是任何标准容器类模板或一些其他专门设计的容器类。容器应可通过随机访问迭代器访问,并支持以下操作:
- empty()
- size()
- front()
- push_back()
- pop_back()
标准容器类vector和deque满足这些要求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用标准容器向量。
需要支持随机访问迭代器,以便始终在内部保持堆结构。这是由容器适配器通过在需要时自动调用算法函数make_heap、push_eap和pop_heap来自动完成的
| 函数声明 | 接口说明 |
| priority_queue()/privority_queue(first,last) | 构造一个空的优先级队列 |
| empty() | 检测优先级队列是否为空,是则返回true,否则返回false |
| top() | 返回优先级队列中最大(最小)元素,即堆顶元素 |
| push(x) | 在优先级队列中插入元素x |
| pop() | 删除优先级队列中最大(最小)元素,即堆顶元素 |
注意:默认情况下priority_queue是大堆
priority_queue的模拟实现
#pragma once #include<vector> namespace happy { template<class T> class less { public: bool operator()(const T& a, const T& b) { return a < b; } }; template<class T> class grater { public: bool operator()(const T& a, const T& b) { return a > b; } }; template<class T,class container=std::vector<T>,class compare=less<T>> class priority_queue { public: void adjust_up(size_t child) { compare com; size_t parent = (child -1)/ 2; while (child > 0) { if (com(_con[parent], _con[child])) { std::swap(_con[parent], _con[child]); child = parent; parent = (child - 1) / 2; } else { break; } } } void adjust_down(size_t parent) { compare com; size_t child = parent * 2 + 1; while (child < _con.size()) { if (child + 1 < _con.size() && _con[child + 1] > _con[child]) { child++; } if (com(_con[child], _con[parent])) { std::swap(_con[child], _con[parent]); parent = child; child = parent * 2 + 1; } else { break; } } } void push(const T& x) { _con.push_back(x); adjust_up(_con.size() - 1); } void pop() { std::swap(_con[0], _con[_con.size() - 1]); _con.pop_back(); adjust_down(0); } const T& top() { return _con[0]; } bool empty() { return _con.empty(); } size_t size() { return _con.size(); } private: container _con; }; }
1万+

被折叠的 条评论
为什么被折叠?



