代码随想录算法训练营第十天|栈和队列、Leetcode232 用栈实现队列、Leetcode225 用队列实现栈
● STL版本
C++STL的版本实现有很多,包括:HP STL、SGI STL、STL Port、P.J.Plauger STL和Rouge Wave STL等。具体在《STL源码剖析》第一章 STL概论与版本简介中1.4 - 1.8中有详细介绍。
三个最普遍的STL版本:
(1)HP STL 其他版本的C++ STL:一般以HP STL为蓝本实现。HP STL是Alexandar Stepanov在惠普Palo Alto实验室工作时,与Meng Lee合作完成的,是C++ STL的第一个实现版本且代码开源;
(2)P.J.Plauger STL 由P.J.Plauger参照HP STL实现出来的,被Visual C++编译器所采用,代码不是开源;
(3)SGI STL 由Silicon Graphics Computer Systems公司参照HP STL实现,主要设计者也是STL之父Alexandar Stepanov,被Linux的C++编译器GCC所采用,SGI STL是开源软件,源码可读性甚高。
其他版本:
(1)STL Port由俄国人Boris Fomitchev通过创建一个免费项目开发STL Port,其目的是为了使SGI STL的基本代码都适用于VC++和C++ Builder等多种编译器。该版本可以跨平台适用于VC++、C++ Builder、GCC等,且能够与BOOST配合使用,实现跨平台代码。STL Port开放源码的;
(2)Rouge Wave STL由Rouge Wave公司参照HP STL实现,用于Borland C++编译器中,这个版本的STL不是开源的。
● 栈和队列
我们需要知道:栈和队列是STL(C++标准库)中的两个数据结构。
我们基于SGI STL里面的数据结构进行讨论其分别的底层实现。
栈
栈(Stack) 是只允许在一端进行插入或删除的线性表。首先栈是一种线性表,但限定这种线性表只能在某一端进行插入和删除操作。
栈提供push和pop等接口,元素操作遵守先进后出的规则,因此栈不提供走访功能,也不提供迭代器。其不能像set和map一样使用iterator遍历元素。
具体在《STL源码剖析》第三章 迭代器概念与traits编程技法中有详细介绍。
迭代器是指针的泛化,允许 C++ 程序使用统一的方式来处理不同的数据结构。 算法对某一种迭代器所指定的值范围起作用,而不是作用于特定的数据类型。 算法可以作用于任何满足迭代器要求的数据结构。
栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的,可以控制使用那种容器来实现栈的功能。因此STL中的栈不能归类为容器,而实container adapter
。
栈的底层实现可以是vector、deque和list,即主要是数组和链表完成其底层实现。
我们可以指定vector为栈的底层实现:
std::stack<int, std::vector<int> > st;
队列
队列(queue) 是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。
队列同样不允许有遍历行为,不提供迭代器, SGI STL中队列一样是以deque为缺省情况下的底部结构。
对于常用的SGI STL,如果没有指定底层实现,默认为deque为缺省下队列的底层结构。
我们可以指定list为队列的底层实现:
std::queue<int, std::list<int>> qu;
● Leetcode232 用栈实现队列
题目链接:Leetcode232 用栈实现队列
视频讲解:代码随想录|用栈实现队列
题目描述:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
说明:
你 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
示例 1:
输入:
[“MyQueue”, “push”, “push”, “peek”, “pop”, “empty”]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
● 解题思路
定义两个stack(_in
和_out
)完成以下操作,_in
完成入栈操作,_out
完成出栈操作。
(1)void push(int x) 将元素 x 推到队列的末尾:
将入队元素放入_in
即可。
(2)int pop() 从队列的开头移除并返回元素:
防止出现错误序列的出队,需要将_in
中的元素一次性放入_out
中。
当_out
为空时,我们循环将_in
元素放入_out
中,然后在_out
中进行出栈操作即可。
(3)int peek() 返回队列开头的元素:
获取队头元素的代码和第二部的代码基本完全重合,因此可以复用int pop()
。但因为在int pop()
中已经将元素弹出,但我们只需要获取元素即可,因此需要将保存元素重新压入_out
中。
(4)boolean empty() 如果队列为空,返回 true ;否则,返回 false:
当_in
和_out
中都没有元素时,证明没有元素在队列中。
时间复杂度:push和empty为O(1), pop和peek为O(n)
空间复杂度:O(n)
● 代码实现
class MyQueue {
public:
//定义两个栈
stack<int> _in, _out;
MyQueue() {
}
//将元素 x 推到队列的末尾
void push(int x) {
_in.push(x);
}
//从队列的开头移除并返回元素
int pop() {
if(_out.empty())
{
while(!(_in.empty()))
{
_out.push(_in.top());
_in.pop();
}
}
int res = _out.top();
_out.pop();
return res;
}
//返回队列开头的元素
int peek() {
int peek = this->pop();
_out.push(peek); //pop弹出对头元素,返回前需要先push进去
return peek;
}
//如果队列为空,返回 true ;否则,返回 false
bool empty() {
if(_out.empty() && _in.empty())
{
return true;
}
else
{
return false;
}
}
};
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue* obj = new MyQueue();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->peek();
* bool param_4 = obj->empty();
*/
● Leetcode225 用队列实现栈
题目链接:Leetcode225 用队列实现栈
视频讲解:代码随想录|用队列实现栈
题目描述:请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
注意:
你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
示例:
输入:
[“MyStack”, “push”, “push”, “top”, “pop”, “empty”]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
● 解题思路
方法一:两个队列
完成了Leetcode232 用栈实现队列,在用队列实现栈中我们也可以使用两个队列来完成。
但因为que1
只是辅助函数,所以我们在pop()
操作完成后还需要将元素放回que1
中并清空que2
;同时在queue
中提供了获取队尾函数back()
,所以简化了我们第三个函数的实现。
时间复杂度:pop()为 O(n),其余为O(1)
空间复杂度:O(n)
方法二:一个队列
因为只使用一个队列,我们在pop()
中需要将size() - 1
个元素重新放入队列中,然后正常执行pop()
即可。
时间复杂度:pop()为 O(n),其余为O(1)
空间复杂度:O(n)
● 代码实现
方法一:两个队列
class MyStack {
public:
queue<int> que1, que2;
MyStack() {
}
//将元素 x 压入栈顶。
void push(int x) {
que1.push(x);
}
//移除并返回栈顶元素。
int pop() {
int size = que1.size();
size--;
while(size--)
{
que2.push(que1.front());
que1.pop();
}
int res = que1.front();
que1.pop();
que1 = que2;
while(!(que2.empty()))
{
que2.pop();
}
return res;
}
//返回栈顶元素。
int top() {
return que1.back();
}
//如果栈是空的,返回 true ;否则,返回 false 。
bool empty() {
return que1.empty();
}
};
/**
* Your MyStack object will be instantiated and called as such:
* MyStack* obj = new MyStack();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->top();
* bool param_4 = obj->empty();
*/
方法二:一个队列完成
class MyStack {
public:
queue<int> que;
MyStack() {
}
void push(int x) {
que.push(x);
}
int pop() {
int size = que.size();
size --;
while(size --)
{
que.push(que.front());
que.pop();
}
int res = que.front();
que.pop();
return res;
}
int top() {
return que.back();
}
bool empty() {
return que.empty();
}
};
/**
* Your MyStack object will be instantiated and called as such:
* MyStack* obj = new MyStack();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->top();
* bool param_4 = obj->empty();
*/