栈的定义:
栈( stack )是限定仅在表尾进行插入和删除操作的线性表
我们把允许插入和删除的一端称为栈顶 top,另一端称为栈底 bottom,不含任何数据元素的栈称为 空栈,栈又称为后进先出的线性表,简称LlFO结构。
首先就要知道栈是一个线性表,具有线性关系(前驱后继关系)。它的特殊之处在于限制了这个线性表的插入和删除的位置,始终只在栈顶进行。
栈的插入操作,叫作进栈,也称压栈、入栈 。
栈的删除操作,叫作出栈,也有的叫作弹栈。
栈的抽象数据类型:
ADT 栈(stack)
Data
同线性表,元素具有相同的类型,相邻元素具有前驱和后继关系
Operation
InitStack(*S) :初始化操作,建立一个空栈S
DestroyStack (*S) :若栈存在 则销毁它
ClearStack (*S) : 将栈清空
StackEmpty (S) :若栈存在且非空,返回 true 否则返回 false
GetTop ( S, *e) :若栈存在且非空,用 e返回 S的栈顶元素
Push (*S ,e) :若栈S存在,插入新元素e到栈S中并成为栈顶元素
Pop (*S,*e):删除栈S中栈顶元素,并用e返回其值
StackLength (s) :返回栈S的元素个数
endADT
栈的顺序存储结构(顺序栈):
数组的话,用下标为0的一端做为栈顶肯定比较好。因为首元素都放在栈底,变化一般是最小的。
top变量是用来指示栈顶元素在数组中的位置所用。
若栈长度为StackSize,则栈顶位置top必须小于StackSize
当栈内只有一个元素时,top等于0,因此空栈的判定条件为top=-1。
typedef int SElemType; //SElemType 类型依据实际情况而定,这里假设为 int
typedef struct
{
SElemType data [MAXSIZE] ;
int top; //用于栈顶指针
}Sqstack;
栈的顺序存储结构-进栈操作:
//插入新元素e为新的栈顶元素
Status Push (Sqstack *S,SElemType e )
{
if (S->top== MAXSIZE -1) //栈满
{
return ERROR;
}
S->top++; //栈顶指针增加一
S->data[S->top]=e; //将新元素赋值给栈顶空间
return OK;
}
栈的顺序存储结构-出栈操作:
/*若栈不空,则删除S的栈顶元素,用 e返回其值,并返回OK,否则返回 ERROR
Status Pop (SqStack *S, SElemType *e)
{
if (S->top==-1)
return ERR0R;
*e=S->data[S->top]; //将要删除的栈顶元素赋值给e
S->top--; //栈顶指针-1
return OK;
}
两者时间复杂度均是O(1);
两栈共享空间:
用一个数组来存储两个栈,可以做到最大限度地利用其事先开辟的存储空间来进行操作。
数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,即下标0处。
另一个栈为栈的末端,即下标为数组长度n-1处。这时两个栈增加元素,就是两端点向中间延伸。
两个栈的指针之间相差1时,即top1 +1 == top2为栈满。
代码实现:
//两栈共享空间结构
typedef struct
{
SElemType data[MAXSIZE];
int top1; //栈1的栈顶指针
int top2l //栈2的栈顶指针
}SqDoubleStack;
因为两个栈共享空间的push方法,除了考虑插入的元素值的参数之外,还需要有一个判断是栈1还是栈2的栈号参数stackNumber。
代码实现:
//插入元素e为新的栈顶元素
Status Push(SqDoubleStack *S,SElemType e,int stackNumber)
{
if(S->top1 +1 == S->top2) //栈已满,不能再push新元素了
return ERROR;
if(stackNumber == 1) //栈1有元素入栈
S->data[++S->top1] = e; //若栈1则先top1+1后给数组元素赋值
else if(stackNumber == 2) //栈2有元素入栈
S->data[--S->top2] = e; //若栈2则先top2-1后给数组元素赋值
return OK;
}
因为开始的时候已经判断是否存在满栈清空,所以插入的时候便可以不再担心溢出的问题。
对于两栈共享空间的pop()方法,参数只需要判断栈1栈2的参数stackNumber
代码实现:
Status Pop(SqDoublestack *S,SElemType *e,int stackNumber)
{
if(stackNumber == 1)
{
if(stackNumber == -1)//没有元素
return ERROR;
*e = S->data[S->top1--];
}
else if(stackNumber == 2)
{
if(stackNumber == MAXSIZE) //没有元素
return ERROR;
*e = S->data[S->top2++];
}
return OK;
}
使用这样的数据结构,通常都是当两个栈的空间需求有相反关系的时候,也就是一个栈增长时另一个栈在缩短的情况。否则两个栈都在不同的增长,很快就会溢出了。但是这种数据结构是针对同一种数据类型,这是需要注意的。
力扣:232. 用栈实现队列https://leetcode.cn/problems/implement-queue-using-stacks/
便用到这一数据结构
栈的链式存储结构(链栈):
栈顶放在单链表的头部,通常对于链栈来说是不需要头结点的。
对于链栈来说基本不存在满栈的情况,而链栈的空就是top == null 的时候
代码实现:
typedef struct StackNode
{
SElemType data;
struct StackNode *next;
}StatckNode,*LinkStachPtr;
typedef struct LinkStack
{
LinkStackPtr top;
int count;
}LinkStack;
栈的链式存储结构-进栈:
//插入元素e是新的栈顶元素
Status Push(LinkStack *S,SElemType e)
{
LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
s->data = e;
s->next = S->top;
S->top = s;
S->count++;
return OK;
}
栈的链式存储结构-出栈:
//若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK,否则ERROR
Status Pop(LinkStack *S,SElemType *e)
{
LinkStackPtr p;
if(StackEmpty(*S)
return ERROR;
*e = S->top->data;
p = S->top;
S->top = S->top->next;
free(p);
S->count--;
return OK;
}
两者时间复杂度均为O(1)。
栈的应用-递归:
递归的定义:我们把一个直接调用自己或者通过一系列的调用语句间接地调用自己的函数,称为递归函数。
迭代跟递归的区别:迭代使用的是循环结构,递归则是选择结构。
递归很符合栈这样的数据结构,就是在前行阶段,对于每一层的递归,函数的局部变量、参数值以及返回地址都被压入栈中。在退回阶段,位于栈顶的局部变量、参数值和返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态。
栈的应用-四则运算表达式求值:
做了解,主要就是由中缀表达式转为后缀表达式,利用栈的特性完成结果。