目录
一.栈和队列相关接口
二.适配器介绍
三.栈和队列模拟实现
四.deque介绍
五.优先级队列
六.优先级队列的模拟实现
1.基本结构
2.插入删除操作
一.栈和队列相关接口
1.栈(Stack)的接口
由于栈接口只能支持栈顶插入(入栈),栈顶删除(出栈),因此接口很少,这里都是熟悉的接口 。如果对于栈和队列不熟悉,可以参考我之前用C语言实现栈和队列的深度解析:
【数据结构】栈和队列-CSDN博客https://blog.csdn.net/2301_80555259/article/details/138817611
2.队列(Queue)的接口
队列相比于栈只多了一个接口:back,用于取队尾数据,队列的front相当于栈的top
二.适配器介绍
无论是栈还是队列,其模板参数中都有一个Container,缺省值为deque<T>,这里就要引入一个叫做“适配器”的概念
适配器是一种设计模式(设计模式就是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该模式是将一个类的接口转换为客户希望的另一个接口。
一个非常典型的例子就是插头的适配器:
那么在数据结构中,这种适配器是什么呢?
在C语言实现栈时,我们实现栈的底层实际上是顺序表,也就是把顺序表做了一层封装和限制,让它的功能变得和栈一样,这里C++也是同理:在实现栈的时候不用再去手搓一个顺序表,可以直接调用库里的vector类!
三.栈和队列模拟实现
和标准库里的模板一样,由于栈要复用其他数据结构,所有在定义模板时有两个模板参数
//deque后续会解释
template<class T, class Container = deque<T>>
class Stack
{
//......
private:
Container _con;
}
这样实现栈就会非常方便,不用写构造函数和析构函数,因为默认生成的构造和析构会去调用内嵌类型的构造和析构帮助我们完成任务
//注:以下的push_back和 back等都是vector中的
void push(const T& val)
{
_con.push_back(val);
}
void pop()
{
_con.pop_back();
}
T& top()//可读可写
{
return _con.back();
}
const T& top() const
{
return _con.back();
}
bool empty() const
{
return _con.empty();
}
size_t size() const
{
return _con.size();
}
栈的实现就完成了,至于队列是很相似的,这里就不再继续实现队列了。
四.deque介绍
deque又被称作双端队列,是一种双开口的“连续”空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,同时时间复杂度都为O(1),与vector比较,头插效率高,不需要频繁挪动元素,而与list相比则空间利用率比较高
接下来看看deque的对应接口:
可以发现deque不仅既有头插尾插,头删尾删,并且还支持方括号[]访问 ,难道它底层也是连续的物理空间吗?
其实deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际上deque类似于一个动态的二维数组,其底层结构如下图:
注意deque有一个中控数组的概念,deque扩容就是直接开辟一份空间,再让中控数组指向新开辟的空间,再将原先空间的内容拷贝至新空间
五.优先级队列
priority_queue(优先级队列)简单介绍
- 优先级队列是一种容器适配器,它的第一个元素总是它包含的元素中最大的(默认是大堆)
- 类似于堆,在堆中可以随时插入元素,并始终在内部保持堆的结构,只能检索最大堆元素(优先级队列中位于最顶部的元素)
- 底层容器可以是任何标准的容器类模板,不过容器需要支持随机迭代器(random access iterator)以及以下功能:
- empty():检测容器是否为空
- size():返回容器中有效元素个数
- front():返回容器中第一个元素的引用
- push_back():在容器尾部插入元素
vector类和deque类都满足这些需求,默认情况下,若没有指定容器,则默认使用vector
优先级队列默认有三个模板参数,第三个就是来决定此优先级队列是大堆还是小堆,它叫仿函数,会下一篇文章讲解,这里先不管它,我们需要做到的是,默认的less是大堆,若我们显式传参greater,那么就是小堆
优先级队列的接口
函数 | 功能 |
---|---|
priority_queue() | 构造一个空的优先级队列 |
priority_queue(first, last) | 用迭代器区间构造一个优先级队列 |
empty() | 检测是否为空 |
top() | 返回优先级队列中最大(最小)元素,即堆顶元素 |
push(x) | 在优先级队列中插入元素x |
pop() | 删除优先级队列中最大(最小)元素,即堆顶元素 |
六.优先级队列的模拟实现
1.基本结构
template<class T, class Container = vector<T>>
class Priority_queue
{
public:
//成员函数
private:
Container _con;//此容器默认是vecto
};
2.插入删除操作
由于优先级队列实际上就是一个堆,所以在插入删除之后要进行向上调整或向下调整以保持堆的结构,那么对应的操作就很熟悉了
//向上调整
void AdjustUp(int* a, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (a[child] > a[parent])
{
swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//向下调整
void AdjustDown(int* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
if (a[child+1] < a[child] && child + 1 < n)
{
child++;
}
if (a[child] < a[parent])
{
swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//堆的插入
void push(const T& val)
{
_con.push_back(val);
adjust_up(_con.size() - 1);
}
//堆的删除
void pop()
{
std::swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjust_down(0);
}
插入和删除可谓是和堆一模一样的做法,其余的函数接口也都是如此,这里就不过多实现了,明白了优先级队列的大致原理即可