栈与队列在数据结构中占据很重要的地位,在今天的学习中我们要初步掌握栈与队列的基本性质以及相关的容器操作,除此之外大家也要对栈与队列的底层实现多加了解。
基础知识点
栈
-
栈(Stack):是只允许在一端进行插入或删除的线性表。
-
栈顶(Top):线性表允许进行插入删除的那一端。
-
栈底(Bottom):固定的,不允许进行插入和删除的另一端。
-
空栈:不含任何元素的空表。
-
栈又称为后进先出(Last In First Out)的线性表,简称LIFO结
在STL容器中,栈有如下基本操作:
初始化——
-
stack<数据类型> 变量名称
功能函数——
-
在栈顶增加元素
void push(value)
-
删除栈顶元素,不返回栈顶元素
void pop()
-
获取栈顶元素
变量名 top()
-
获取栈的大小
int size()
-
判断是否为空
bool empty()
-
返回队列长度
int size()
队列
-
队列(queue):队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,是一种操作受限制的线性表。
-
队尾(rear):进行插入操作的那一端。
-
队头(front):进行删除操作的那一端。
-
空队列:没有元素的队列。
-
因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。
在STL容器中,队列有如下基本操作:
初始化——
-
queue<数据类型> 变量名称
功能函数——
-
将value压入队列的末端
void push(value)
-
弹出队列的第一个元素(队顶元素),函数不返回任何值
void pop()
-
返回队顶元素
变量名 front()
-
返回队尾元素
变量名 rear()
-
判断是否为空
bool empty()
-
返回队列长度
int size()
题目练习
LeetCode232.用栈实现队列
题目链接
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
视频讲解
栈的基本操作! | LeetCode:232.用栈实现队列_哔哩哔哩_bilibili
题目分析
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(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(双端队列)来模拟一个栈,只要是标准的栈操作即可。
根据一般经验,题干越长,题目就越简单,一长串的内容全都是纸老虎,其实就是让我们借助两个栈来实现队列的操作然后完成一个类的书写。
实现操作的示意图如下:
首先需要定义两个stack,分别为StackIn和StackOut。
stack<int> StackIn;
stack<int> StackOut;
实现队列中的push功能时,我们只需要将数据插入到StackIn中即可,无需进行其他操作。
void push(int x) {
StackIn.push(x);
}
实现队列中的pop功能时,首先要注意,此时pop函数要求移除队顶元素并返回队顶元素。
这时StackOut的栈就发挥了作用,我们需要将StackIn中所有的元素统统移动到StackOut中(这样才能确保出栈的顺序与队列的操作一致),然后从StackOut中获取并去除栈顶元素即可,此时就实现了队列的pop功能。
代码如下:
int pop() {
if(StackOut.empty()) {
while(!StackIn.empty()) {
StackOut.push(StackIn.top());
StackIn.pop();
}
}//将StackIn中的元素统统挪到StackOut中去。
int result = StackOut.top();//获取栈顶元素,也就是队列的队顶元素
StackOut.pop();//删除栈顶元素,也就是移除队列的队顶元素
return result;
}
实现peek功能时,因为只需要返回队顶元素,所以我们可以借助this指针直接调用pop进行操作,然后将pop操作中删除的栈顶元素再push到StackOut中即可。
int peek() {
int result = this -> pop();
StackOut.push(result);
return result;
}
当然如果不使用this指针,也可以直接将StackIn中的元素统统挪到StackOut中去,获取栈顶元素(即队顶元素),两者时间复杂度大致相当。
int peek() {
if(StackOut.empty()) {
while(!StackIn.empty()) {
StackOut.push(StackIn.top());
StackIn.pop();
}
}//将StackIn中的元素统统挪到StackOut中去。
return StackOut.top();
}
但是当我们做大型工程项目的时候,最好是采用第一种方法。因为一旦pop出现毛病,那么在第一种方法中我们只需要对pop进行修改就可以,不需要再对其他函数进行修正;但若是采用第二种方法,也就是复制pop中的相同操作到peek中,一旦复制的部分内容出现错误,那么随之而来的就是需要在所有有这种操作的函数中进行修改,但这种操作被应用到很多很多个函数中时,一旦改不完全,就是一个巨大的定时炸弹,到时候出现的bug会造成很严重的后果。
所以推荐大家使用this指针进行操作,当然能操作的前提还是这两个函数在一个类中。
最终实现empty函数时只需要判断StackIn和StackOut是不是同时为空就可以,若同时为空就说明队列为空,否则队列不空。
bool empty() {
if(StackIn.empty() && StackOut.empty()) {
return true;
}
else {
return false;
}
}
LeetCode225. 用队列实现栈
题目链接
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
视频讲解
队列的基本操作! | LeetCode:225. 用队列实现栈_哔哩哔哩_bilibili
题目分析
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
同样是长题干,同样也是纸老虎,这道题就是让我们用两个队列实现一个栈的功能。
此时Queue2就只是作为一个备份队列在起作用了。我们将数据存入Queue1后,依次将数据出列并储存到Queue2中,知道Queue1中还剩一个元素,也就是队尾元素,此时这个队尾元素就是Stack中需要出栈的元素。
将这个目标元素出栈之后,我们再将Queue2赋值给Queue1即可,之后可以再进行出栈操作。
要注意,既然Queue2是作为备份队列进行使用,那么在每次Queue2给Queue1赋值完后,Queue2自身要清空,否则会影响之后的赋值。
这就是pop函数的大致实现思路,而其他的push、peek、empty函数与前面一题的思想基本一致,所以在这里不再啰嗦了。
class MyStack {
public:
//初始化两个队列
queue<int> QueueOperator;
queue<int> QueueCopy;
MyStack() {
}
//将元素插入到队列中也就是插入到栈中
void push(int x) {
QueueOperator.push(x);
}
//移除并返回栈顶元素
int pop() {
while(QueueOperator.size() > 1) {
QueueCopy.push(QueueOperator.front());
QueueOperator.pop();
}//将队列1中队尾元素之前的元素全部移到队列二中
int result = QueueOperator.front();//获取此时队列1中的队顶元素,即栈顶元素
QueueOperator = QueueCopy;//队列2给队列1赋值
while(!QueueCopy.empty()) QueueCopy.pop();//清空队列2
return result;
}
//返回栈顶元素
int top() {
int result = this -> pop();
QueueOperator.push(result);
return result;
}
//如果栈是空的,返回 true ;否则,返回 false 。
bool empty() {
return QueueOperator.empty() && QueueCopy.empty();
}
};
总结
今天主要学习了stack和queue的容器使用方法,真的很方便。
今天的内容比较基础,操作起来比较简便,思想比较好掌控,但是除了容器操作之外,我们还需要花点时间了解一下stack和queue的底层代码实现,拿一部分内容还是比较不容易的,大家可以参考一下下面这篇文章。
链接🔗数据结构:栈和队列(Stack & Queue)【详解】_UniqueUnit的博客-CSDN博客
加油💪!