👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨
目录
- 一、queue的基本概念
- 二、 queue的常见操作
- 2.1 构造函数
- 2.2 empty
- 2.3 size
- 2.4 front
- 2.5 back
- 2.6 push
- 2.7 pop
- 2.8 赋值操作
- 三、有关队列的力扣经典题
- 3.1 二叉树的层序遍历
- 3.2 用队列实现栈
- 四、模拟实现queue
- 4.1 简介
- 4.2 代码实现
- 五、deque(了解)
- 5.1 deque的介绍
- 5.2 deque的底层原理
- 5.3 deque的缺陷
- 5.4 为什么选择deque作为stack和queue的底层默认容器
一、queue的基本概念
queue
是一种容器适配器,也是一种先进先出(First in First Out
,简称FIFO
)的数据结构, 其中从容器一端插入元素,另一端提取元素- 容器适配器通常会限制对底层容器的访问方式,因此不能遍历,但队列中只有
二、 queue的常见操作
2.1 构造函数
- 默认构造
// T可以是任意类型
queue<T> q;
- 拷贝构造
//q已知
queue<T> qq(q);
2.2 empty
功能:检测队列是否为空,是返回
true
,否则返回false
2.3 size
功能:返回队列中有效元素的个数
2.4 front
功能:返回队头元素
2.5 back
功能:返回队尾元素
2.6 push
功能:在队尾将元素
val
入队列
2.7 pop
功能:将队头元素出队列
2.8 赋值操作
#include <iostream>
#include <queue>
using namespace std;
int main()
{
queue<int> q;
// 插入
q.push(10);
q.push(20);
q.push(30);
q.push(40);
queue<int> qq;
//赋值运算符
qq = q;
cout << "元素个数:" << qq.size() << endl;
cout << "队头元素" << qq.front() << endl;
cout << "队尾元素" << qq.back() << endl;
// 遍历
while (!qq.empty())
{
cout << qq.front() << ' ';
qq.pop();
}
cout << endl;
return 0;
}
【输出结果】
三、有关队列的力扣经典题
3.1 二叉树的层序遍历
链接:点击跳转
【题目描述】
【思路】
【代码实现】
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root)
{
queue<TreeNode*>q;
vector<vector<int>> vv;
int levelSize;
// 如果根节点不为空,则入队列
if (root)
{
q.push(root);
// 并且根节点的root一定为1
levelSize = 1;
}
while (!q.empty())
{
// 一层一层出
vector<int> v;
for (int i = 0;i < levelSize;i++)
{
// 记录当前节点
TreeNode* front = q.front();
// 删除节点并存入
q.pop();
v.push_back(front->val);
// 并带入它的子节点
if (front->left) q.push(front->left);
if (front->right) q.push(front->right);
}
vv.push_back(v);
// 一层出完更新一层的个数
levelSize = q.size();
}
return vv;
}
};
3.2 用队列实现栈
链接:点击跳转
【题目描述】
【思路】
队列的特点是先进先出,而栈是先进后出,首先定义两个队列
那如何模拟一个栈呢?首先往空的队列入数据
对于栈来说,先出的是4。因此我们可以把1 2 3移到另一个空队列中
【代码实现】
class MyStack {
public:
MyStack() {}
void push(int x)
{
// 往不是空的队列插入数据
if (in.empty())
{
out.push(x);
}
else
in.push(x);
}
int pop()
{
// 保持一个队列为空
// 将一个为空的队列的前n-1个移到空队列
// 剩下的那个这是栈顶元素
if (in.empty())
{
while (out.size() > 1)
{
int front = out.front();
out.pop();
in.push(front);
}
int ans = out.front();
out.pop();
return ans;
}
else // out为空
{
while (in.size() > 1)
{
int front = in.front();
in.pop();
out.push(front);
}
int ans = in.front();
in.pop();
return ans;
}
}
int top()
{
if (in.empty())
{
return out.back();
}
else
{
return in.back();
}
}
bool empty()
{
return in.empty() && out.empty();
}
private:
queue<int> in;
queue<int> out;
};
四、模拟实现queue
4.1 简介
queue
同样也是一种容器适配器,容器适配器可以被视为一种包装器,它们通过修改底层容器的接口或行为来实现新的功能。通过使用这些容器适配器,开发者可以方便地在不同场景下使用已有容器的功能,并且无需关心底层容器的具体实现。
其实就是STL
中封装好的队列,在使用的时候我们不仅可以指定内部的数据类型,还可以指定内部的容器。不指定容器其实也是可以的,模板参数有一个缺省值,默认是deque
4.2 代码实现
#pragma once
namespace wj
{
template<class T, class Container = deque<T>>
class queue
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_front();
}
const T& front()
{
return _con.front();
}
const T& back()
{
return _con.back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
注意:队列就不能用vector
适配了,因为vector
没有提供pop_front
接口,但是也可以强制适配。只需要改动pop
接口
void pop()
{
_con.erase(_con.begin());
}
但注意,库里是没有强制适配的
为了贴近库,最好不要进行强制适配~
那么为什么库里不支持vector的头删呢?就是因为效率低,头删需要移动数据。
五、deque(了解)
5.1 deque的介绍
deque
(双端队列):是一种可以在头尾两端进行插入和删除的"连续"空间的数据结构,且时间复杂度为O(1)
,它似乎是vector
和list
的合体版。与vector
比较,deque
头插效率高,不需要搬移元素;与list
比较,空间利用率比较高(是一块连续的空间)。
5.2 deque的底层原理
但如果深挖底层的话,deque
并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque
类似于一个动态的二维数组,其底层结构如下图所示:
为了兼顾双端插入以及随机访问,deque
的底层是使用一个中控数组(可以认为是指针数组)来管理一个个连续的空间,且第一个空间被开辟出来后是存放在中控数组的中央位置,之后不断插入数据,若一块连续空间已满只需要再开一块连续空间即可。也就是在中控数组中再增加一个指针。若是进行头插,则需要开辟一段新空间,将新的值存于连续空间的尾部。
5.3 deque的缺陷
- 与
vector
比较,deque
的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是比vector
高的。 - 与
list
比较,deque
底层是连续空间,空间利用率比较高, - 但是,
deque
有一个致命缺陷:不适合遍历,因为在遍历时,deque
的迭代器要频繁的去检测其是否移动到
某段小空间的边界,导致效率低下。大多数情况下优先考虑vector
和list
。还有中部的插入删除操作相当复杂,若是直接在中部插入就要挪动当前空间的数据,更甚者还要牵扯到接下来的连续空间 deque
的应用并不多,而目前能看到的一个应用就是,STL
用其作为stack
和queue
的底层默认容器
5.4 为什么选择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
的优点,而完美的避开了其缺陷。