引言:
上次我们学习了容器list
的使用及其底层实现,相对来说是比较复杂的,今天我们要学习的适配器stack
和queue
与list
相比就简单很多了,下面我们就开始今天的学习:
一:stack
(后进先出)
1. 约定:
由于之前我们在数据结构初阶阶段已经了解过stack
这个容器了,因此这里就不再具体来介绍了。
2. stack
的介绍
stack
的介绍文档
3. stack
的使用
stack()
: 构造一个空栈。empty()
:判断栈是否为空。size()
:返回栈中的数据个数。top()
:返回栈顶数据。push()
:将元素压入栈中。pop()
:将栈顶元素弹出栈。
代码演示:
二:queue
(先入先出)
1. 约定:
由于之前我们在数据结构初阶阶段已经了解过queue
这个容器了,因此这里就不再具体来介绍了。
2. queue
的介绍:
queue
的介绍文档
3. queue
的使用:
queue()
: 构造一个空队列 。empty()
: 判断队列是否为空。size()
: 返回队列中数据个数。push()
: 将数据加入队列。pop()
: 将队头数据出队。front()
: 返回队头数据。back()
: 返回队尾数据。
代码演示:
三:priority_queue
1. 约定:
这里的priority_queue
就可以对比之前我们在数据结构初阶的时候学习的堆,因此这里的priority_queue
也就不作具体介绍了。
2. priority_queue
的介绍:
优先级队列默认使用vector
作为其底层存储数据的容器,在vector
上又使用了堆算法将vector
中元素构造成堆的结构,因此priority_queue
就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue
。
默认情况下priority_queue
是大堆 。
priority_queue
的具体介绍文档
3. priority_queue
的使用:
priority_queue()
:构造一个空的堆。priority_queue(first,last)
: 用迭代器区间构造一个优先队列。empty()
: 判断堆是否为空。top()
: 返回堆顶数据。push()
: 将数据入堆。pop()
: 删除堆顶数据。
代码演示:
1. 普通构造:
注:这里的数据构成二叉树的话是满足大根堆的。
2. 迭代器构造:
3. 自定义实现大根堆、小根堆
对于内置类型的话:
如果这里我想要创建小根堆的话就需要自己传入仿函数来实现:
如果是自定义类型的话,创建大根堆和小根堆都需要有相应的<
和 >
运算符重载,下面拿日期类来举例:
这是我们实现的一个日期类:
四:priority_queue
的模拟实现
1. 仿函数:
(1)定义:
仿函数(Functor),也称为函数对象(Function Object),是C++中通过重载operator()
运算符的类或结构体,使其能够像函数一样被调用。
(2)形式:
注:我们这里的仿函数写成了模版的形式,契合泛型编程的思想,适用范围更广。
这是我们实现的一个小于的仿函数:
这是我们实现的一个大于的仿函数:
2. 基本框架:
注:这里我们是按照大根堆来实现的,想实现小根堆只需修改第三个参数即可。
注:这里的第二个参数container
和第三个参数compare
别忘了实例化。
3. 向上调整算法:
4. 向下调整算法:
5. 入堆:
6. 出堆:
7. 取堆顶:
8. 判空:
9. 求数据个数:
10. 测试:
五:容器适配器
1. 定义:
适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。
2. STL标准库中stack和queue的底层结构:
虽然stack
和queue
中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack
和queue
只是对其他容器的接口进行了包装,STL中stack
和queue
默认使用deque
,比如:
可以看到上面的这几个数据结构都是其他容器的封装。
3. deque(了解)
(1)原理介绍:
deque(双端队列)
:是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1)
,与vector
比较,头插效率高,不需要搬移元素;与list
比较,空间利用率比较高。
deque
并不是真正意义上的连续空间,而是由一段段连续空间连接起来的,类似于一个动态的二维数组。
其底层结构如下图:
它的底层实现比较复杂(不需要深入学习)
双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,这个责任就落在了deque
的迭代器身上,因此deque
的迭代器设计就比较复杂,如下图所示:
4. deque的缺陷:
与vector
比较,deque
的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是比vector
高的。
与list
比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。
但是,deque
有一个致命缺陷:不适合遍历,因为在遍历时,deque
的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector
和list
,deque
的应用并不多,而目前能看到的一个应用就是,STL用其作为stack
和queue
的底层数据结构。
5. 思考:为什么选择deque作为stack和queue的底层默认容器?
stack
是一种后进先出的特殊线性数据结构,因此只要具有push_back()
和pop_back()
操作的线性结构,都可以作为stack
的底层容器,比如vector
和list
都可以;queue
是先进先出的特殊线性数据结构,只要具有push_back
和pop_front
操作的线性结构,都可以作为queue
的底层容器,比如list
。但是STL中对stack
和queue
默认选择deque
作为其底层容器,主要是因为:
stack
和queue
不需要遍历(因此stack
和queue
没有迭代器),只需要在固定的一端或者两端进行操作。- 在
stack
中元素增长时,deque
比vector
的效率高(扩容时不需要搬移大量数据);queue
中的元素增长时,deque
不仅效率高,而且内存使用率高。结合了deque
的优点,而完美的避开了其缺陷(不需要随机访问,避免了频繁调动[]
)。
六:stack的模拟实现
1. 基本框架:
2. 入栈:
3. 出栈:
4. 取栈顶:
5. 求栈中数据个数:
6. 判空:
7.测试: