目录
栈与队列理论基础
问题:
-
C++中stack 是容器么?
-
我们使用的stack是属于哪个版本的STL?
-
我们使用的STL中stack是如何实现的?
-
stack 提供迭代器来遍历stack空间么?
栈和队列是C++标准库(STL)里提供的两个数据结构。
栈
栈符合先进后出的原则,C++中的栈提供 push 和 pop 接口。栈不提供走访功能,也不提供迭代器,因此也无法用迭代器遍历所有元素。栈是用底层容器来完成所有工作,对外提供统一接口,而底层容器是可插拔的,即可以控制使用哪种容器来实现栈的功能。栈的底层实现可以是vector,deque,list等。
学习参考:https://c.biancheng.net/view/6971.html
C++中常见的栈操作:
成员函数 | 功能 |
---|---|
empty() | 当 stack 栈中没有元素时,该成员函数返回 true;反之,返回 false。 |
size() | 返回 stack 栈中存储元素的个数。 |
top() | 返回一个栈顶元素。如果栈为空,程序会报错。 |
push(const T& val) | 将元素压入栈顶。 |
pop() | 弹出栈顶元素。 |
队列
队列中先进先出的数据结构,同样不允许有遍历行为,不提供迭代器。所以STL 队列也不被归类为容器,而被归类为 container adapter(容器适配器)。
学习参考:C++ STL queue容器适配器详解
C++中队列的常见操作:
成员函数 | 功能 |
---|---|
empty() | 如果 queue 中没有元素的话,返回 true。 |
size() | 返回 queue 中元素的个数。 |
front() | 返回 queue 中第一个元素。 |
push(const T& obj) | 在 queue 的尾部添加一个元素。 |
pop() | 删除 queue 中的第一个元素 |
C++的容器适配器
C++中的序列式容器有很多,如 vector、list、deque,而容器适配器就是通过封装某个序列式容器,并重新组合该容器中包含的成员函数,使其满足某些特定场景的需要。在STL中的容器适配器,其内部使用的基础容器并不是固定的,用户可以在满足特定条件的多个基础容器中自由选择。在C++中,栈适配器 stack 和队列适配器 queue 默认使用的底层容器是 deque 双端队列。
容器适配器 | 基础容器筛选条件 | 默认使用的基础容器 |
---|---|---|
stack | 基础容器需包含以下成员函数: empty() size() back() push_back() pop_back() 满足条件的基础容器有 vector、deque、list。 | deque |
queue | 基础容器需包含以下成员函数: empty() size() front() back() push_back() pop_front() 满足条件的基础容器有 deque、list。 | deque |
用栈实现队列
题干
题目:请你仅使用两个栈实现先入先出队列。应当支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
push(x) -- 将一个元素放入队列的尾部。 pop() -- 从队列首部移除元素。 peek() -- 返回队列首部的元素。 empty() -- 返回队列是否为空。
你只能使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
思路
方法一:让队头元素对应栈顶,队尾元素对应栈底
①push(x) -- 将一个元素放入队列的尾部:队列是先进先出的,所以将元素放入队列尾部,就是最晚弹出,最晚弹出就相当于要放到栈的最底层。要将元素放到栈的底层,就得先把原栈里的所有元素弹出,并暂存在另一个栈空间中,再将要插入的元素压入原栈,此后把暂存在另外栈空间中的其他元素压回原栈中。时间复杂度O(2n)
②pop() -- 从队列首部移除元素:既然栈顶元素存放的是队头,那么直接弹出栈顶元素即可。时间复杂度O(1)
③peek() -- 返回队列首部的元素:从栈顶读出元素。时间复杂度 O(1)
④empty() -- 返回队列是否为空:和判断栈是否为空是相同的,栈空说明队列也空。时间复杂度O(1)
方法二:使用输入栈和输出栈
Q1:如何理解输入栈和输出栈?
输入栈就相当于队列的入队入口,输出栈就相当于出队出口。
Q2:如何使用输入栈和输出栈完成对应的队列操作?
①当 push(x) 操作要插入元素到队尾时,直接压入 输入栈 中。
②当要 pop() 弹栈 或读取队首元素 peek() 时,如果输出栈为空,则把输入栈的所有元素压入输出栈中,再从输出栈弹出元素;若输出栈不为空,则直接弹出输出栈的栈顶。
③如何判断队列是否为空?由于我们使用了两个栈同时存储队列元素,那么只有当两个栈都为空时队列才为空。
代码
方法一:让队头元素对应栈顶,队尾元素对应栈底
class MyQueue {
public:
stack<int> myQueue;
// 创建、初始化队列
MyQueue() {
}
// 插入元素到队尾
void push(int x) {
stack<int> tmp;
// 将栈中其他元素暂存在另一个栈中,清空栈再插入元素
while (!myQueue.empty()){
tmp.push(myQueue.top());
myQueue.pop();
}
// 在栈底插入新元素
myQueue.push(x);
// 将暂存的其他元素压回原栈
while (!tmp.empty()){
myQueue.push(tmp.top());
tmp.pop();
}
}
// 从栈的首部弹出队头元素
int pop() {
int num = myQueue.top();
myQueue.pop();
return num;
}
// 返回队列首部的元素
int peek() {
return myQueue.top();
}
// 判断队列是否为空
bool empty() {
return myQueue.empty();
}
};
方法二:输入栈和输出栈
class MyQueue {
public:
stack<int> in;
stack<int> out;
MyQueue() {
}
// 时间复杂度 O(1)
void push(int x) {
in.push(x);
}
int pop() {
// 时间复杂度为 O(n)
int num = peek();
out.pop();
return num;
}
int peek() {
if (out.empty()){
// 要遍历整个栈,所以时间复杂度为 O(n)
while (!in.empty()){
out.push(in.top());
in.pop();
}
}
int num = out.top();
return num;
}
// 时间复杂度 O(1)
bool empty() {
if (in.empty() && out.empty()){
return true;
} else{
return false;
}
}
};
用队列实现栈
题干
题目:请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。你只能使用队列的标准操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
实现 MyStack 类:
-
push(x) -- 元素 x 入栈
-
pop() -- 移除栈顶元素
-
top() -- 获取栈顶元素
-
empty() -- 返回栈是否为空
思路
方法一:使用一个队列实现栈
当使用一个队列实现栈时,关键操作就是弹出的元素应该是队尾元素。如果队列的长度是 size,那么就需要把队尾元素的前 size - 1 个元素依次从队头弹出又插入回队尾,此时之前的队尾元素就会移动到队头了。
方法二:使用两个队列实现栈
使用两个队列,关键是在每次插入新元素时使新元素变成队头,弹栈时只需弹出非空队列的队头元素,这样就和栈的后进先出原则符合。
如何实现?我们需要保持两个队列中的一个队列为空,每次插入新元素时都插入到空队列,这样新元素就可以作为空队列的队头,而剩下的另一个队列中的元素依次弹出插入到新元素的末尾,这样就可以一直保持每次插入的新元素在队头的位置。
代码
方法一:使用一个队列实现栈
class MyStack {
public:
queue<int> stack;
MyStack() {
}
void push(int x) {
stack.push(x);
}
// pop() 时间复杂度O(n)
int pop() {
int count = stack.size();
// 将除了队尾以外的前面所有元素重新插入队尾,也就是 count - 1 个元素
while (count > 1){
int tmp = stack.front();
stack.pop();
stack.push(tmp);
count--;
}
// 此时队头就是要弹出的元素
int num = stack.front();
stack.pop();
return num;
}
int top() {
int num = pop();
stack.push(num);
return num;
}
bool empty() {
return stack.empty();
}
};
方法二:使用两个队列实现栈
class MyStack {
public:
queue<int> que1;
queue<int> que2;
MyStack() {
}
// 时间复杂度 O(n)
void push(int x) {
if (que1.empty() && que2.empty()){
que1.push(x);
} else if (que1.empty()){
que1.push(x);
while (!que2.empty()){
que1.push(que2.front());
que2.pop();
}
} else if (que2.empty()){
que2.push(x);
while (!que1.empty()){
que2.push(que1.front());
que1.pop();
}
}
}
int pop() {
int num;
if (!que1.empty()){
num = que1.front();
que1.pop();
} else if (!que2.empty()){
num = que2.front();
que2.pop();
}
return num;
}
int top() {
int num;
if (!que1.empty()){
num = que1.front();
} else if (!que2.empty()){
num = que2.front();
}
return num;
}
// 当两个队列都为空时才说明栈空
bool empty() {
return que1.empty()&&que2.empty();
}
};