栈 stack的定义
1,栈是限定仅在表尾进行插入和删除操作的线性表
2,允许插入和删除的一端称为栈顶 top,另一端称为栈底 bottom,不含任何数据元素的栈称为空栈。 栈又称为 后进先出 LIFO 的线性表。
注意:栈是一种特殊的线性表
3,插入操作,叫做进栈,也叫压栈,入栈;删除操作,叫做出栈,也叫弹栈。
4,进栈出栈的变化形式
最重要的是保证是栈顶元素出栈就行,而不是对元素进行的时间进行限制。
栈的抽象数据类型
ADT 栈( stack )
Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
Operation
InitStack(*S):初始化操作,建立一个空栈S。
DeatoryStack(*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
栈的顺序存储结构
1,栈的结构定义
typedef int SElemType; //SElemType类型根据实际情况而定,这里假设为int
typedef struct
{
SElemType data[MAXSIZE];
int top; //用于栈顶指针
} SqStack;
2,进栈操作
/*插入元素e为新的栈顶元素*/
Status Push(SaStack *S,SElemType e)
{
if (S->top == MAXSIZE -1 )
{
return ERROR;
}
S->top++; //栈顶指针+1
S->data[S->top] = e;
return OK;
}
3,出栈操作
/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status Pop ( SqStack *S, SElemType *e)
{
if (S->pop == -1)
return ERROR;
*e = S->data[S->top];
S->top--;
return OK;
}
入栈和出栈操作没有涉及到任何循环语句,因此时间复杂度均是O(1)。
两栈共享空间
1,用一个数组来存储两个栈
让一个栈的栈底为数组的始端,另一个栈的栈底为数组的末端。
数组长度为n
栈1满:top1 = n-1;
栈2满:top2 = 0;
栈满:top1+1 == top2
/*两栈共享空间结构*/
typedef struct
{
SElemType data[MAXSIZE];
int top1;
int top2;
}SqDoubleStack;
2,插入操作 push
/*插入元素e为新的栈顶元素*/
//stackNumber为栈号参数
Status Push ( SqDoubleStack *S, SElemType e, int stackNumber)
{
if (S->top1 + 1 == S->top2 ) return ERROR;//栈满
if (stackNumber == 1)
S->data[++S->top1] = e;
else if (stackNumber == 2)
S->data[--S->top2] = e;
return OK;
}
2,弹出操作pop
/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status Pop (SaDoubleStack *S, SELemType *e, int stackNumber)
{
if (stackNumber == 1)
{
if (S->top1 == -1)
return ERROR;
*e = S->data[S->top1--];
}
else if (stackNumber == 2)
{
if (S->top2 == MAXSIZE)
return ERROR;
*e = S->data[S->top2++];
}
return OK;
}
两栈共享空间,两个栈要具有相同的数据类型,适用于一个栈增长时另一个栈在缩短的情况 。
栈的链式存储结构及实现
1,栈的链式存储结构,简称链栈。
结构代码:
//结点
typedef struct StackNode
{
SElemType data;
struct StackNode *next;
}StackNode,*LinkStackPtr
//链栈
typedef struct LinkStack
{
LinkStackPtr top;
int count;
}LinkStack;
2,进栈操作push
/*插入元素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;
}
3,出栈操作pop
/*若栈不空,则删除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;
}
链栈的push和pop操作都很简单,没有任何循环操作,时间复杂度均为O(1)。
对比顺序栈和链栈
时间复杂度均为O(1)
空间上顺序栈需要事先确定一个固定的长度,可能存在内存空间浪费。而链栈则要求每个元素都有指针域,同时也增加了一些内存开销,但是对于栈的长度无限制。
如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,最好用链栈;如果变化在可控范围内,使用顺序栈可能会更好一点。
栈的应用——递归
1,斐波拉契数列
/*常规迭代打印出斐波拉契的前40项*/
int main()
{
int i;
int a[40];
a[0] = 0;
a[1] = 1;
for(i=2;i<40li++)
{
a[i] = a[i-1] + a[i-2];
printf("%d",a[i]);
}
return 0;
}
/*斐波拉契递归函数*/
int Fbi (int i)
{
if (i<2)
return i == 0 ? 0 : 1;
return Fbi(i-1) + Fbi(i-2);
}
int main()
{
int i;
for (int i=0;i<40;i++)
printf("%d", Fbi(i) );
return 0;
}
理解递归小技巧:不要把递归函数中调用自己的函数看作是在调用自己,就当是在调用另一个函数。
2,递归定义
我们把一个直接调用自己或者通过一系列的调用语句间接地调用自己的函数,称为递归函数。
递归定义必须至少有一个条件,满足时递归不再进行,即不再引用自身而是返回值退出。
在前行阶段,对于每一层递归,函数的局部变量、参数值以及返回地址都被压入栈中;在退回阶段,位于栈顶的局部变量、参数值和返回地址都被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态。
栈的应用——四则运算表达式求值
逆波兰表达式/后缀表达式
1 ,中缀表达式转换成后缀表达式:
从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于栈顶符号(乘除有限加减),则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。
2,后缀表达式计算规则:从左到右遍历表达式的每个数字和符号,遇到数字就进栈,遇到符号就将栈顶两个数字出栈,进行运算,**运算结果进栈,**一直到最终获得结果。