之前我们讲了vector和list的模拟实现,本质上就是实现一个顺序表和单链表。今天我们要实现的是stack和queue。我们在学习数据结构的时候,相信大家都手撕过这两个玩意,我们再来回顾一下这两个的特点。
首先是栈,当时我们是用顺序表实现的,其特点是先进后出,我们选取一端作为栈底,也就是顺序表下表为0的位置,栈顶的位置用来进出数据。
然后是队列,当时我们是用链表来实现的,因为队列是先进后出,涉及到一个尾插和头删的行为,如果我们仍然选择顺表,在头删的时候需要挪动数据,那么时间复杂度就是n^2,所以我们选择了单链表。
在我们学习了模板之后,我们对于stack和queue进行了一个改进,原来我们只能存类型,现在我们可以通过容器的方式来存自定义类型了。(例如string等)
当然在模拟实现的时候,我们为了统一性,选取的数据结构我们改为了deque。这里我们简单的讲解一下deque。
这里是我从网站上截取的片段,可以看到,我们通过deque进行以上所有操作,而这种操作对于stack和queue都是适用的。那么这么神通广大的数据结构,是怎么实现的呢?我们来看看底层。
这里我简单的画了一下:
可以看到,它涉及了很多指针,首先是左边一列下来的表,是用来存只想不同顺序表的地址的。然后在其内部,又有很多指针,来指向顺序表的头尾等,如果尾插,直接在顺序表后插入即可,头插的话,就在那一列的表中,再开一个顺序表即可。由此我们可以推断出,deque是从中间的位置开始存储数据的。由于deque有两端都可以进行操作,并且时间复杂度十分可观,我们也管它叫做双端队列。
好了在介绍完双端队列之后,我们来看看模拟实现stack和queue。
template<class T, class Con = 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.size();
}
bool empty()const
{
return _c.empty();
}
private:
Con _c;
};
template<class T, class Con = deque<T>>
class queue
{
public:
queue()
{
}
void push(const T& x)
{
_c.push_front();
}
void pop()
{
_c.pop_back();
}
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;
};
到这里是没有什么难度的,就是正确的使用queue中的方法。并且在使用时选择的容器要能够支持这些功能。