栈与队列
理论基础
栈提供push 和 pop 等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。 不像是set 或者map 提供迭代器iterator来遍历所有元素。
栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(也就是说我们可以控制使用哪种容器来实现栈的功能)。
所以STL中栈往往不被归类为容器,而被归类为container adapter(容器适配器)。
那么问题来了,STL 中栈是用什么容器实现的?
从下图中可以看出,栈的内部结构,栈的底层实现可以是vector,deque,list 都是可以的, 主要就是数组和链表的底层实现。
我们常用的SGI STL,如果没有指定底层实现的话,默认是以deque为缺省情况下栈的底层结构。
deque是一个双向队列,只要封住一段,只开通另一端就可以实现栈的逻辑了。
队列中先进先出的数据结构,同样不允许有遍历行为,不提供迭代器, SGI STL中队列一样是以deque为缺省情况下的底部结构。
也可以指定list 为起底层实现。所以STL 队列也不被归类为容器,而被归类为container adapter( 容器适配器)。
232.用栈实现队列
232. Implement Queue using Stacks
题目链接/文章讲解/视频讲解:https://programmercarl.com/0232.%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.html
代码:
//思路:
//出队列pop就是把最早的拿出(取首元素peek同理),即如果输出栈为空(即输入栈和输出栈都为空),就把输入栈(最早的在最里面)全导入输出栈,再取出来的最外面的就是最早的;如果输出栈非空,就直接取出最外面的就好
//入队列push就是把元素放到末尾,直接放入数据栈即可
//返回队列的首元素peek,即如果输出栈非空,就直接取出最外面的顶部元素,否则取出输入栈的首元素
//判断队列为空,即如果输入栈和输出栈都为空则队列为空,否则不为空
//释放队列的内存空间,就是将输入栈和输入栈顶索引重置为-1
//注:
//return obj->in[0];//首元素不在顶部时可查询但不可直接操作
//思路:
//出队列pop就是把最早的拿出(取首元素peek同理),即如果输出栈为空(即输入栈和输出栈都为空),就把输入栈(最早的在最里面)全导入输出栈,再取出来的最外面的就是最早的;如果输出栈非空,就直接取出最外面的就好
//入队列push就是把元素放到末尾,直接放入数据栈即可
//返回队列的首元素peek,即如果输出栈非空,就直接取出最外面的顶部元素,否则取出输入栈的首元素
//判断队列为空,即如果输入栈和输出栈都为空则队列为空,否则不为空
//释放队列的内存空间,就是将输入栈和输入栈顶索引重置为-1
//注:
//return obj->in[0];//首元素不在顶部时可查询但不可直接操作
typedef struct {
int outTop;//输出栈顶索引
int inTop;//输入栈顶索引
//因为题目说最多调用 100 次 push、pop、peek 和 empty,所以栈大小取100
int out[100];//输出栈
int in[100];//输入栈
} MyQueue;
//创建栈指针
//先分配内存空间,再初始化栈顶索引为-1,最后返回指针
MyQueue* myQueueCreate() {
MyQueue* queue =(MyQueue*)malloc(sizeof(MyQueue));
queue->outTop=-1;
queue->inTop=-1;
return queue;
}
//入队列push就是把元素放到末尾,直接放入数据栈即可
void myQueuePush(MyQueue* obj, int x) {
obj->inTop++;// 输入栈顶索引加1
obj->in[obj->inTop]=x;// 将元素x放入输入栈
}
//出队列pop就是把最早的拿出(取首元素peek同理),即如果输出栈为空,就把输入栈(最早的在最里面)全导入输出栈,再取出来的最外面的就是最早的;如果输出栈非空,就直接取出最外面的顶部元素就好
int myQueuePop(MyQueue* obj) {
int temp;
//如果输出栈为空,就把输入栈(最早的在最里面)全导入输出栈
if(obj->outTop==-1){
while(obj->inTop>-1){
obj->out[++obj->outTop]=obj->in[obj->inTop--];
}
}
//取出输出栈最外面的顶部元素
temp=obj->out[obj->outTop--];
return temp;
}
//返回队列的首元素peek,即如果输出栈非空,就直接取出最外面的顶部元素,否则取出输入栈的首元素
int myQueuePeek(MyQueue* obj) {
if(obj->outTop>-1){
return obj->out[obj->outTop];
}
return obj->in[0];//首元素不在顶部时可查询但不可直接操作
}
//判断队列为空,即如果输入栈和输出栈都为空则队列为空,否则不为空
bool myQueueEmpty(MyQueue* obj) {
if((obj->outTop==-1)&&(obj->inTop==-1)){
return true;
}
return false;
}
//释放队列的内存空间,就是将输入栈和输入栈顶索引重置为-1
void myQueueFree(MyQueue* obj) {
obj->inTop=-1;
obj->outTop=-1;
}
/**
* Your MyQueue struct will be instantiated and called as such:
* MyQueue* obj = myQueueCreate();
* myQueuePush(obj, x);
* int param_2 = myQueuePop(obj);
* int param_3 = myQueuePeek(obj);
* bool param_4 = myQueueEmpty(obj);
* myQueueFree(obj);
*/
参考资料:
代码随想录算法训练营第十天| 232.用栈实现队列 、 225. 用队列实现栈https://blog.csdn.net/weixin_48369273/article/details/135703310
Typedef Struct 用法详解和用法小结https://www.cnblogs.com/lzjsky/archive/2010/11/24/1886717.html
225. 用队列实现栈
225. Implement Stack using Queues
题目链接/文章讲解/视频讲解:https://programmercarl.com/0225.%E7%94%A8%E9%98%9F%E5%88%97%E5%AE%9E%E7%8E%B0%E6%A0%88.html
不熟的知识点:
rear尾指针指向的是尾元素后一位
虽然front和rear是int类型,但相当于数组的第front和rear位,可以作为指示队列开头结尾在数组中的位置指针,所以front不一定为0
虽然front和rear是int类型,但相当于数组的第front和rear位,可以作为指示队列开头结尾在数组中的位置指针,所以front不一定为0
代码:
//思路:
//用两个队列实现pop就是将队列1中除了首元素的所有元素放入队列2,再从队列2中放回队列1
//用一个队列实现pop就是将除了首元素的所有元素依次取出再从尾部加入队列
//思路:
//用两个队列实现pop就是将队列1中除了首元素的所有元素放入队列2,再从队列2中放回队列1
//用一个队列实现pop就是将除了首元素的所有元素依次取出再从尾部加入队列
#define maxSize 100//定义队列的最大容量为100,因为题目规定最多调用100 次 push、pop、top 和 empty
typedef struct {
int front1,rear1;
int data1[maxSize];
} MyStack;
//创建新的队列,就是先分配内存,再将首尾指针都赋值为0,返回新分配的队列指针
MyStack* myStackCreate() {
MyStack* temp=(MyStack*)malloc(sizeof(MyStack));
temp->front1=0;
temp->rear1=0;
return temp;
}
//向栈中压入一个元素push,就是队列尾部加入元素移动尾指针即可
void myStackPush(MyStack* obj, int x) {
if(obj->front1==(obj->rear1+1)%maxSize) return;//如果front1等于(rear1+1)%MAX_SIZE,说明循环队列已满,退出函数
obj->data1[obj->rear1]=x;
obj->rear1=(obj->rear1+1)%maxSize;
}
//从栈中弹出一个元素pop,就是将除了首元素的所有元素依次取出再从尾部加入队列
int myStackPop(MyStack* obj) {
int temp=obj->rear1;//temp记录原尾指针位置
while((obj->front1+1)%maxSize!=temp){//如果相等,即首指针移动到原来尾指针的前一位(即原来的尾元素位置),即把除了尾元素的所有元素都遍历一遍
obj->data1[obj->rear1]=obj->data1[obj->front1];//将首元素赋值给尾元素后一位
obj->rear1=(obj->rear1+1)%maxSize;//将尾指针后移
obj->front1=(obj->front1+1)%maxSize;//将首指针后移继续遍历元素
}
int temp1=obj->data1[obj->front1];//temp1记录首指针位置,原来的尾元素位置
obj->front1=(obj->front1+1)%maxSize;//将首指针后移,即从队列中将尾元素弹出
return temp1;//返回原来尾元素位置
}
//返回栈顶元素的值,就是先pop取出栈顶元素,再push把栈顶元素压回去
int myStackTop(MyStack* obj) {
int temp=myStackPop(obj);
myStackPush(obj,temp);
return temp;
}
//判断栈是否为空,即判断队列首尾指针是否相同
bool myStackEmpty(MyStack* obj) {
if(obj->front1==obj->rear1) return true;
return false;
}
//释放栈,就是让队列首指针移动到尾指针处
void myStackFree(MyStack* obj) {
obj->front1=obj->rear1;
}
/**
* Your MyStack struct will be instantiated and called as such:
* MyStack* obj = myStackCreate();
* myStackPush(obj, x);
* int param_2 = myStackPop(obj);
* int param_3 = myStackTop(obj);
* bool param_4 = myStackEmpty(obj);
* myStackFree(obj);
*/
参考资料:
【数据结构】手写循环顺序队列【纯c语言版】
https://blog.csdn.net/weixin_43113381/article/details/121363822
使用C语言实现队列https://blog.csdn.net/Keep_Trying_Go/article/details/126289222
如果front1等于(rear1+1)%MAX_SIZE,说明循环队列已满,退出函数
代码随想录算法训练营第十天| 232.用栈实现队列 、 225. 用队列实现栈https://blog.csdn.net/weixin_48369273/article/details/135703310
20. 有效的括号
题目链接/文章讲解/视频讲解:https://programmercarl.com/0020.%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.html
知识点:链栈
使用C语言实现链栈(带头结点和不带头结点)
https://blog.csdn.net/Keep_Trying_Go/article/details/126284714
解决报错:
解决报错1(error: conflicting types for ‘stackEmpty’; have ‘_Bool(struct stackNode *)’ [solution.c]):因为定义ElemType stackPop函数在定义bool stackEmpty函数之前,而ElemType stackPop函数中用了bool stackEmpty函数,调换两函数顺序即可解决。
解决报错2(Line 13: Char 5: error: unknown type name ‘stackNode’ [solution.c]):用typedef struct stackNode和struct stackNode* next;
如下,
//定义栈节点结构体,包含数据data,指向下一个节点的指针next
typedef struct stackNode{
ElemType data;
struct stackNode* next;
}stackNode,*linkStack;//定义栈节点类型和指向栈节点的指针类型
代码:
//思路(用链表实现的栈):
//如果是左符号就将对应的右符号压入栈中;如果是其他字符,对比时栈为空或符号不匹配返回0
//最后对比遍历完了栈不为空,返回0,否则返回1
//思路(用链表实现的栈):
//如果是左符号就将对应的右符号压入栈中;如果是其他字符,对比时栈为空或符号不匹配返回0
//最后对比遍历完了栈不为空,返回0,否则返回1
//定义元素类型为int
#define ElemType int
//定义栈节点结构体,包含数据data,指向下一个节点的指针next
typedef struct stackNode{
ElemType data;
struct stackNode* next;
}stackNode,*linkStack;//定义栈节点类型和指向栈节点的指针类型
//初始化栈,就是分配内存和将栈节点置空,再返回初始化过的栈
linkStack stackIni(){
stackNode* temp=(stackNode*)malloc(sizeof(stackNode));
temp->next=NULL;
return temp;
}
//入栈操作,就是为新节点分配内存、存入数据与位置next,最后插入到栈顶
void stackPush(linkStack stack,ElemType data){
stackNode* temp=(stackNode*)malloc(sizeof(stackNode));
temp->data=data;
temp->next=stack->next;
stack->next=temp;
}
//判断栈是否为空,如果栈顶指针为空(即没有下一个元素),表示栈为空
bool stackEmpty(linkStack stack){
return stack->next==NULL;
}
//出栈操作,就是如果栈为空返回-1,如果栈不为空就取出顶部元素,并用下一个结点替代原栈顶指针,最后释放节点内存返回栈顶数据
ElemType stackPop(linkStack stack){
if(stackEmpty(stack)) return -1;
stackNode* temp=stack->next;//指向栈顶结点
stack->next=temp->next;//栈顶指针指向下一个节点,替代原栈顶指针
int tempData=temp->data;//保存栈顶数据
free(temp);//释放栈顶节点内存
return tempData;//返回栈顶数据
}
//判断括号序列是否有效
//如果是左符号就将对应的右符号压入栈中;如果是其他字符,对比时栈为空或符号不匹配返回0
//最后对比遍历完了栈不为空,返回0,否则返回1
bool isValid(char* s) {
linkStack stack=stackIni();
ElemType data;
for(int i=0;i<strlen(s);i++){
switch(s[i]){
case'(':stackPush(stack,')');break;
case'{':stackPush(stack,'}');break;
case'[':stackPush(stack,']');break;
default:if((stackEmpty(stack))||(stackPop(stack)!=s[i])) return 0;
}
}
if(stackEmpty(stack))return 1;
return 0;//否则返回0
}
卡哥给出的c语言版(数组栈):
//辅助函数:判断栈顶元素与输入的括号是否为一对。若不是,则返回False
int notMatch(char par, char* stack, int stackTop) {
switch(par) {
case ']':
return stack[stackTop - 1] != '[';
case ')':
return stack[stackTop - 1] != '(';
case '}':
return stack[stackTop - 1] != '{';
}
return 0;
}
bool isValid(char * s){
int strLen = strlen(s);
//开辟栈空间
char stack[5000];
int stackTop = 0;
//遍历字符串
int i;
for(i = 0; i < strLen; i++) {
//取出当前下标所对应字符
char tempChar = s[i];
//若当前字符为左括号,则入栈
if(tempChar == '(' || tempChar == '[' || tempChar == '{')
stack[stackTop++] = tempChar;
//若当前字符为右括号,且栈中无元素或右括号与栈顶元素不符,返回False
else if(stackTop == 0 || notMatch(tempChar, stack, stackTop))
return 0;
//当前字符与栈顶元素为一对括号,将栈顶元素出栈
else
stackTop--;
}
//若栈中有元素,返回False。若没有元素(stackTop为0),返回True
return !stackTop;
}
参考资料:
代码随想录算法训练营第十一天| 20. 有效的括号 、1047. 删除字符串中的所有相邻重复项 、 150. 逆波兰表达式求值https://blog.csdn.net/weixin_48369273/article/details/135717260
1047. 删除字符串中的所有相邻重复项
1047. Remove All Adjacent Duplicates In String
而且在企业项目开发中,尽量不要使用递归!在项目比较大的时候,由于参数多,全局变量等等,使用递归很容易判断不充分return的条件,非常容易无限递归(或者递归层级过深),造成栈溢出错误(这种问题还不好排查!)
代码:
//思路:
//每拿到一个字母就去和栈顶元素对比,栈为空或和栈顶元素不相等则入栈,相等则出栈
//最后在栈顶指针位置添加字符串结束符后输出字符串数组即可(如果当作栈弹出全部元素还需要反转,所以最后直接当数组处理)
//知识点:
//数组栈数据结构包含栈顶索引和数组,初始化就是分配内存空间再初始化栈顶索引为-1
//思路:
//每拿到一个字母就去和栈顶元素对比,栈为空或和栈顶元素不相等则入栈,相等则出栈
//最后在栈顶指针位置添加字符串结束符后输出字符串数组即可(如果当作栈弹出全部元素还需要反转,所以最后直接当数组处理)
//知识点:
//数组栈数据结构包含栈顶索引和数组,初始化就是分配内存空间再初始化栈顶索引为-1
char* removeDuplicates(char* s) {
//分配内存(数组栈长度为字符串长度+1,因为要多出一个位置存放字符串结束符)
char* stack=(char*)malloc(sizeof(char)*(strlen(s)+1));
//初始化栈顶索引为-1
int stackTop=-1;
//遍历字符串,栈为空或和栈顶元素不相等则入栈,相等则出栈
for(int i=0;i<strlen(s);i++){
if((stackTop==-1)||stack[stackTop]!=s[i]) stack[++stackTop]=s[i];
else stackTop--;
}
//在栈顶指针位置添加字符串结束符
stack[++stackTop]='\0';
return stack;
}
参考资料:
代码随想录算法训练营第十一天| 20. 有效的括号 、1047. 删除字符串中的所有相邻重复项 、 150. 逆波兰表达式求值https://blog.csdn.net/weixin_48369273/article/details/135717260