欢迎来到繁星的CSDN,本期内容主要包括stack(栈),queue(队列),priority_queue(堆)
实际上,C++中的stack、queue还有priority_queue与C语言中的内容无异,所以本篇文章主要想去阐述的,是有关stack、queue还有priority_queue的一些接口与实现原理。
一、deque
在说明栈、队列以及优先队列是如何实现之前,我们必须阐述deque的相关知识。
deque是什么?为什么要有deque?以及deque在模拟实现中扮演着什么样的角色?
当我们翻到stl库里的stack和queue的头文件时(只需要在VS2022所在盘搜索stl_queue.h和stl_stack.h即能得到其头文件,这边推荐使用everything软件辅助快速寻找)我们可以看到Sequence的缺省值是deque<_Tp>,Sequence在后续的代码中不难发现是起容器的作用,而_Tp则扮演了stack内存储值的类型。
deque是什么以及为什么用deque
deque实际上是vector与list的结合体,由多个vector组成,并以list的形式串连起来。
这么做的好处是什么?
让我们回想stack和queue的特点,一个是先进先出,一个是后进先出。如果都用vector来储存的话,那么stack需要的是pop_back(),queue需要的是pop_front()。但是用vector来pop_back简单,pop_front就难上加难了,用list的话首尾删除插入倒是方便,但是在存储空间上由于存储的地址不连续性,以及list每个结点所需要存储的信息更多,使得整体耗费的存储空间更大,且首插尾插效率很低。
于是deque,一个结合了vector与list的东西诞生了。
很可惜的是,事实上deque结合了vector和list的优点,更结合了vector与list的缺点。进行尾删操作尚且简单,如果进行头删则需要将其他位置所有的数据向前搬一格。如果进行插入操作,则每次插入都有可能多一个同样长度的vector,一插一删之间,deque很可能造成大量的空间浪费,亦或是牺牲了“搬迁”的效率。
或许在平均效率上deque比单纯用vector或者list更高吧,但stl创建者选用deque的原因我们不得而知。
二、stack和queue的接口
stack和queue的接口是十分类似的。
push(x);
pop();
empty();
size();
top();//stack
front();//queue
back();//queue
与string的区别是,string是push_back,但stack和queue是push。string是pop_back,但stack和queue是pop。queue的front和back分别代表第一个元素以及最新被push的元素。stack的top表示栈顶,没有栈底接口。
为什么要坚持用接口?
这一点实际上在封装的那一部分已经被提到过了。我们用接口的目的一方面是为了更简单地使用这些复杂的数据结构,一方面是为了数据的安全性,我们只能使用接口对这些结构进行改造,而不能直接从底层的vector或者deque改数据,这种操作对于结构而言是“不合法”的(对于程序而言并没有什么问题)。
三、priority_queue
优先队列(priority_queue)实际上是堆,其优先性区分了大根堆与小根堆,但值得注意的是,如果需要使用priority_queue,我们需要包的头文件是<queue>。
template<class T,class Container = std::vector<T>,class Compare = std::less<typename Container::value_type>> class priority_queue;
默认的优先队列是大根堆,在日常使用中我们仅仅需要输入堆所存储的数据类型即可。如果需要小根堆,那么就需要输入数据类型,存储容器(一般为vector),以及std::greater<>(递增)。或者自己书写仿函数。
优先队列的常用接口如下:
push(x);
pop();
top();
empty();
size();
其中push放入后,优先队列会自动进行排序,top即堆的根,pop即弹出堆的根,size和empty就不过多赘述了。
下面是大致的模拟实现。
#pragma once
#include<deque>
template<class T,class Con = std::deque<T>>
class stack
{
public:stack();void push(const T& x) {_c.push_back(x);}void pop() {_c.pop_back();}T& top() {return _c.back();}const T& top()const {return _c.back();}size_t size()const {return _c.back();}bool empty()const {return _c.empty();}
private:Con _c;
};
#pragma once
#include<deque>
template<class T, class Con = std::deque<T>>class queue
{
public:queue();void push(const T& x) {_c.push_back(x);}void pop() {_c.pop_front();}T& back() {return _c.back();}const T& back()const {return _c.back();}T& front() {return _c.front();}const T& front()const {return _c.front();}size_t size()const {return _c.size();}bool empty()const {return _c.empty();}
private:Con _c;
};
#pragma once
namespace bit
{
#include<vector>
#include<functional>template <class T, class Container = std::vector<T>, class Compare = std::less<T> >class priority_queue{public:priority_queue();template <class InputIterator>priority_queue(InputIterator first, InputIterator last) {std::make_heap(first,last,comp);}bool empty() const {return c.empty();}size_t size() const {return c.size();}T& top() const {return c.front();}void push(const T& x) {x.push_back(x);std::push_heap(c.begin(), c.end(), comp);}void pop() {std::pop_heap(c.begin(), c.end(), comp);c.pop_back();}private:Container c;Compare comp;};
};