1、栈的概念
是一种特殊的线性表,只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
普通的线性表实现是有两个端口可以访问的,但是如果作为栈就要封闭一端,只能访问另一端。换言之,栈是一种抽象数据结构,是对现实世界对象的模拟。例如,像是餐厅中的一叠盘子,新的盘子总是会放在这一叠盘子的最上面,取得时候也是从最上面取,这种形式将其抽象出来就是栈,这是最合适的抽象方式。
所以,基于栈的操作非常简单:
1)将数据压入栈顶-push
2)将栈顶数据弹出-pop
3)查看栈顶数据-top
栈属于常见的一种线性结构,对于进栈和退栈而言,时间复杂度都为 O(1)。
借用他人描述的一个举例:
函数调用栈:A(B(C(D()))): 即 A 函数中调用 B,B 调用 C,C 调用 D;在 A 执行的过程中会将 A 压入栈,随后 B 执行时 B 也被压入栈,函数 C 和 D 执行时也会被压入栈。所以当前栈的顺序为:A->B->C->D(栈顶);函数 D 执行完之后,会弹出栈被释放,弹出栈的顺序为 D->C->B->A;
递归: 为什么没有停止条件的递归会造成栈溢出?比如函数 A 为递归函数,不断地调用自己(因为函数还没有执行完,不会把函数弹出栈),不停地把相同的函数 A 压入栈,最后造成栈溢出(Queue Overfloat)。
栈的ADT定义
栈的简单结构体定义:
typedef int ElemType
typedef struct Stack
{
ElemType data[MAXSIZE];
int top; //栈顶指针
}Stack;
栈的基本操作
1)InitStack(&S):初始化空栈S
2)StackEmpty(S):判断一个栈是否为空
3)Push(&S,x):进栈,若栈未满,则将x加入使之成为新栈顶
4)Pop(&S,&x):出栈,若栈非空,则将栈顶元素,并用x返回
5)GetTop(S,&x):读栈顶元素,若栈顶元素非空,则用x返回栈顶元素
6)DestroyStack(&S):销毁栈,并释放栈S占用的存储空间
2、顺序栈的结构体定义
采用顺序存储方式的栈称为顺序栈,是由一组地址连续的存储单元来存放自栈底至栈顶的数据元素,同时会有一个指针(top)指示当前栈顶的位置,并由一个maxsize指定容量即栈中元素额最大个数。
#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 100
typedef int SElemType;
typedef struct{
SElemType *base;
SElemType *top;
int stacksize;//栈的最大容量
}SqStack;
其中,栈顶指针:top,初始时设置top = -1;
栈顶元素:data[top];
进栈操作:栈不满时,栈指针加1,再送值到栈顶元素;
出栈操作:栈非空时,先去栈顶元素值,再将栈顶指针减1;
栈空条件:top == -1;
栈满条件:top == MAXSIZE - 1;
栈长:top + 1;
2.1、栈的初始化
void init(SqStack &S){
//定义一个数组,长度为MAXSIZE
S.base=new SElemType[MAXSIZE];
if(!S.base) exit; //如果不存在就退出
S.top=S.base; //栈顶和栈底指向相等
S.stacksize=MAXSIZE; //栈的长度
}
2.2、入栈
void push(SqStack &S,SElemType e){
if(S.top-S.base==S.stacksize) printf("该栈已满");
*S.top=e;
S.top++;
printf("入栈成功");
}
2.3、出栈
void pop(SqStack &S,SElemType e){
if(S.top==S.base) printf("该栈为空");
S.top--;
e=*S.top;
printf("出栈成功");
}
2.4、取栈顶的值
SElemType getTop(SqStack S){
//栈非空时返回
if(S.top!=S.base){
return *(S.top-1);
}
}
2.5、遍历栈内的所有元素
//从栈顶开始遍历
void printStackTop(SqStack S){
printf("栈顶:");
while(S.top!=S.base){
printf("%d ",*(S.top-1));
S.top--;
}
printf("\n");
}
//从栈底开始遍历
void printStackBottom(SqStack S){
printf("栈底:");
SElemType *p=S.base;
while(p!=S.top){
printf("%d ",*p);
p++;
}
printf("\n");
}
3、栈与一般线性表的区别
4、栈的应用
应用场景:括号的匹配、递归运算、进制的转换等;
括号检测:
class Solution:
def isValid(self, s: str) -> bool:
stack = [“hh”]
for i in s:
if i in [“(”, “{”, “[”]:
stack.append(i)
elif (i == “)” and stack[-1] == “(”) or (i == “}” and stack[-1] == “{”) or (i == “]” and stack[-1] == “[”):
stack.pop()
else:
return False
return len(stack) == 1
代码说明:
首先我们使用栈来保存读取过的字符串中的括号,如果括号为"(“、”{“、”[“这三种则将其压入栈中,如果为”)“、”}“、”]"则需要与栈顶元素对比,如果括号匹配上了,则执行下一个循环,如果匹配错误,则直接返回无效。
参考资料:数据结构与算法基础-王卓老师