目录
简介
栈(Stack)和队列(Queue)是两种基本的数据结构,在STL(Standard Template Library,标准模板库)中,它们被封装为容器适配器,用于管理元素的插入和删除操作。
栈(Stack)
栈是一种后进先出(Last In First Out, LIFO)的数据结构。在STL中,栈的接口定义如下:
- push():在栈顶插入一个元素。
- pop():删除栈顶元素。
- top():返回栈顶元素的引用。
- empty():检查栈是否为空。
- size():返回栈中元素的数量。
栈通常用于以下场景:
- 递归算法的实现。
- 实现后退功能,如浏览器的后退按钮。
- 解决问题时的临时存储,如深度优先搜索(DFS)。
队列(Queue)
队列是一种先进先出(First In First Out, FIFO)的数据结构。在STL中,队列的接口定义如下:
- push():在队列末尾插入一个元素。
- pop():删除队列首部的元素。
- front():返回队列首部元素的引用。
- back():返回队列末尾元素的引用。
- empty():检查队列是否为空。
- size():返回队列中元素的数量。
队列通常用于以下场景:
- 在多任务处理中管理任务,如线程池。
- 实现广度优先搜索(BFS)。
- 缓存实现,如最近最少使用(LRU)缓存。
在STL中,栈和队列通常是基于deque(双端队列)或list(链表)来实现的,但用户不需要关心底层实现细节,可以直接使用它们提供的接口来进行操作。这两种数据结构都是模板类,可以存储任何类型的元素。
由于我们需要深度学习这两种数据结构,因此本文将实现stack与queue
实现
栈的实现
栈可分数组站、链表栈,都可以实现后进先出的功能。我们可以采用C语言中借助一个动态指针等方式实现。但是在C++中,可以借助其他容器实现。
对于stack而言,既可以使用list,也可以使用vector、、、
template<class T, class Container = std::deque<T>>
class stack
{
官网中stack的实现借助的是模板
因此我们也借助模板实现。
模板参数由数据类型T和容器类型Container构成。
栈的功能函数
栈主要是top pop push这三个核心函数构成。
namespace My_Stack
{
template<class T, class Container = std::deque<T>>
class stack
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
const T& top() const
{
return _con.back(); //back不是front
}
bool empty() const
{
return _con.empty();
}
size_t size() const
{
return _con.size();
}
private:
Container _con; //默认生成的构造函数,会调用_con的构造
};
}
需要注意的是:
对于构造函数、析构函数等内置函数,我们并没有显式实现。系统默认生成的构造函数会自动调用_con该容器的构造,析构函数也会调用该容器的析构完成资源的清理。
top是栈顶元素,也就是容器的最后一个元素。
队列的实现
队列也是一个模板类,但是队列的容器不能是vector,原因是vector没有pop_front()函数,而队列需要大量的先进先出。
#include <deque>
namespace My_Queue
{
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();
}
const T& back()
{
return _con.back();
}
const T& front() const
{
return _con.front();
}
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
private:
Container _con;
};
}
deque的讲解
可以看到,我们在实现队列和栈的时候,都利用了deque,这是什么呢?
Deque(双端队列)是一种数据结构,它允许在队列的两端进行插入和删除操作。它是“double-ended queue”的缩写,有时也被称为双端队列容器。
在 C++ 的 STL(标准模板库)中,deque 与 vector 容器有很多相似之处,但它们之间也存在一些关键的区别。与 vector 不同的是,deque 在队列的两端(头部和尾部)添加或删除元素的效率都非常高,时间复杂度为 O(1)。这意味着 deque 在频繁进行头部或尾部操作时,比 vector 更高效。
其实deque同样可以成为完成栈和队列的容器。
以下是 deque 的一些特点:
-
双向开口:deque 是一种双向开口的连续线性空间,允许在头尾两端分别进行元素的插入和删除操作。
-
动态空间管理:deque 没有固定的容量概念,它是动态地以分段连续空间组合而成,可以根据需要随时增加新的空间。
-
高效的头尾操作:与 vector 相比,deque 在头部进行插入和删除操作时效率更高,因为不需要移动所有元素。
-
迭代器:deque 的迭代器需要能够指出分段连续空间的位置,并且能够判断是否处于当前缓冲区的边缘。如果是,则在进行前进或后退操作时,需要跳跃到下一个或上一个缓冲区。
如下是deque的函数
看函数,像是list与vector的结合体。它既能实现list的功能,也能实现vector的功能。那为什么不能deque呢?主要还是,他什么都会,但是什么都不精通!
因此,根据需求,我们还是主要使用list和vector。但是,当需要在序列的两端频繁进行添加或删除操作时,deque 容器是一个很好的选择。
deque的结构示意图
deque其实是一个中控数组实现,数组中存放着一个个的指针,指向了其他容器(一般被乘坐buffer)。
其实,本质是个指针数组!
头插与尾插示意图
头插:在10之前
中控满了,可以去扩容,但是代价低,因为这只是一个指针数组,只需要拷贝指针。
但是在内部插入删除元素,就显得效率极其低下!因为要么得整体挪动数据,以保证buffer的大小一致,方便[]访问;要么得对单个的buffer扩容,或者减少数据。