1、栈的定义
1.栈的定义
先进去,却要后出来,而后进的反而可以先出来的数据结构---栈
例子:浏览器的后退键,点击后可以按访问顺序的逆序加载浏览过的网页
栈是限定仅在表尾进行插入和删除操作的线性表
我们把允许插入和删除的一端称为栈顶(top),另外一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出的线性表
进栈:
栈的插入操作,也称压栈、入栈
出栈:
栈的删除操作,也叫弹栈
2.进栈出栈变化形式
最先进栈的元素,不一定最后出栈 比如,如果有3个整型数字元素1、2、3依次进栈,会有如下的出栈次序:
第一种:1、2、3进,再3、2、1出。出栈次序321
第二种:1进,1出,2进,2出,3进,3出。出栈次序123
第三种:1进,2进,2出,1出,3进,3出。出栈次序213
第四种:1进,1出,2进,3进,3出,2出。出栈次序132
第五种:1进,2进,2出,3进,3出,1出。出栈次序231
3.栈的抽象数据类型
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
4.栈的顺序存储结构及实现
4.1 栈的顺序存储结构
定义一个top变量指示栈顶元素在数组中的位置,通常把空栈的判定条件定位top等于-1
typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int */
/* 顺序栈结构 */
typedef struct
{
SElemType data[MAXSIZE];
int top; /* 用于栈顶指针 */
}SqStack;
4.2 栈的顺序存储结构---进栈操作
/* 插入元素e为新的栈顶元素 */
Status Push(SqStack *S,SElemType e)
{
if(S->top == MAXSIZE -1) /* 栈满 */
{
return ERROR;
}
S->top++; /* 栈顶指针增加一 */
S->data[S->top]=e; /* 将新插入元素赋值给栈顶空间 */
return OK;
}
4.3 栈的顺序存储结构---出栈操作
/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(SqStack *S,SElemType *e)
{
if(S->top==-1)
return ERROR;
*e=S->data[S->top]; /* 将要删除的栈顶元素赋值给e */
S->top--; /* 栈顶指针减一 */
return OK;
}
5.两栈共享空间
数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,即下标为0处,另一个栈为数组的末端,即下标为数组长度n-1处
关键思路
它们是在数组的两端,向中间靠拢
栈1为空时,就是top1等于-1时;而当top2等于0时,即是栈2为空时。
极端情况: 若栈2是空栈,栈1的top1等于n-1时,就是栈1满了 当栈1为空栈时,top2等于0时,为栈2满
当两个指针之间相差1时,即top1+1==top2为栈满
/* 两栈共享空间结构 */
typedef struct
{
SElemType data[MAXSIZE];
int top1; /* 栈1栈顶指针 */
int top2; /* 栈2栈顶指针 */
}SqDoubleStack;
插入元素代码: (stackNumber用于判断是栈1还是栈2)
/* 插入元素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;
}
删除元素的代码:
/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(SqDoubleStack *S,SElemType *e,int stackNumber)
{
if (stackNumber==1)
{
if (S->top1==-1)
return ERROR; /* 说明栈1已经是空栈,溢出 */
*e=S->data[S->top1--]; /* 将栈1的栈顶元素出栈 */
}
else if (stackNumber==2)
{
if (S->top2==MAXSIZE)
return ERROR; /* 说明栈2已经是空栈,溢出 */
*e=S->data[S->top2++]; /* 将栈2的栈顶元素出栈 */
}
return OK;
}
使用两栈共享空间这样的数据结构,通常都是当两个栈的空间需求有相反关系时,也就是一个栈增长时另一个栈在缩短的情况
只针对两个具有相同数据类型的栈的设计
6.栈的链式存储结构及实现
6.1 栈的链式存储结构
typedef struct StackNode{
SElemType data;
struct StackNode *next;
}StackNode, *LinkStackPtr;
typedef struct LinkStack{
LinkStackPtr top;
int count;
}LinkStack;
6.2 栈的链式存储结构---进栈操作
对于链栈的进栈push操作,假设原素值为e的新结点是s,top为栈顶指针
/*插入元素e为新的栈顶元素*/
Status Push(LinkStack *S, SElemType e){
LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
s->data = e;
s->next = S->top;/*把当前的栈顶元素赋值给新结点的直接后继,如图中①*/
S->top = s;/*将新的结点s赋值给栈顶指针,如图中②*/
S->count++;
return OK;
}
6.3 栈的链式存储结构---出栈操作
链栈的出栈pop操作,也是简单的三句操作。假设变量p用来存储要删除的栈顶结点,将栈顶指针下移一位,最后释放p即可
/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(LinkStack *S,SElemType *e)
{
LinkStackPtr p;
if(StackEmpty(*S))
return ERROR;
*e=S->top->data;
p=S->top; /* 将栈顶结点赋值给p,见图中③ */
S->top=S->top->next; /* 使得栈顶指针下移一位,指向后一结点,见图中④ */
free(p); /* 释放结点p */
S->count--;
return OK;
}
链栈的进栈push和出栈pop操作都很简单,没有任何循环操作,时间复杂度均为0(1)
对于空间性能,顺序栈需要事先确定一个固定的长度,可能会存在内存空间浪费问题,但它的优势是存取时定位很方便,而栈链则要求每个元素都有指针域,这同时增加了一些内存开销,但对于栈的长度无限制。
如果栈的使用过程中元素变化不可预料,有时很小,有时很大,那么最好是用链栈,反之,如果它的变化在可控范围内,建议使用顺序栈会更好一些
7.栈的作用
8.栈的应用---递归
9.栈的应用---四则运算表达式求值
9.1 后缀(逆波兰)表示法定义
对于“9 + (3 - 1) * 3 + 10 / 2”,可以用后续表示法表示为“9 3 1 - 3 * + 10 2 / +”
一种不需要括号且所有的符号都是在要运算数字的后面出现,即为后缀表示法
9.2 后缀表达式计算结果
规则:从左到右遍历式的每个数字和符号,遇到是数字就进栈你,遇到是符号,就将处于栈顶的两个数字出栈,进行运算,运算结果出栈,一致到最终获得结果
1、初始化一个空栈。此栈用来对要运算的数字进出使用
2、后缀表达式中前三个都是数字,所以9、3、1进栈
3、接下来是“-”,所以将栈中的1出栈作为减数,3出栈作为被减数,并运算3-1得到2,再将2进栈
4、接着是数字3进栈
5、后面是“*”,也意味着栈中3和2出栈,2与3相乘,得到6,并将6进栈
6、下面是“+”,所以栈中6和9出栈,9与6相加,得到15,将15进栈
7、接着是10与2两数字进栈
8、接下来是符号“/”,因此,栈顶的2与10出栈,10与2相除,得到5,将5进栈
9、最后一个是符号“+”,所以15与5出栈并相加,得到20,将20出栈
10、结果是20出栈,栈变为空
9.3 中缀表达式转后缀表达式
平时所用的标准四则运算表达式,即"9+(3-1)X3+10÷2 "叫做中缀表达式
规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级不高于栈顶符号(乘除优先加减)则栈顶元素依次出栈并输出,并将当前符号进栈,直到最终输出后缀表达式为止
- 初始化一空栈,用来对符号进出栈使用
- 第一个字符是数字9,输出9,后面是符号“+”,进栈
- 第三个字符是“(”,依然是符号,因其只是左括号,还未配对,故进栈
- 第四个字符是数字3,输出,总表达式为9 3,接着是“-”,进栈
- 接下来是数字1,输出,总表达式为9 3 1,后面是符号“)”,此时,我们需要去匹配此前的“(”,所以栈顶依次出栈,并输出,直到“(”出栈为止。此时左括号上方只要“-”,因此输出“-”。总的输出表达式为9 3 1-
- 紧接着是符号“X”,因为此时的栈顶符号为“+”号,优先级低于“X”,因此不输出,“*”进栈,紧接着是数字3,输出,总的表达式为9 3 1 - 3
- 之后是符号“+”,此时当前栈顶元素“*”比这个“+”的优先级高,因此栈中元素出栈并输出(没有比“+”号更低的优先级,所以全部出栈),总输出表达式为9 3 1 - 3 * +
然后将当前这个符号“+”进栈。 - 紧接着数字10,输出,总表达式变为9 3 1 -3 * +10.后是符号“+” ,所以“/”进栈
- 最后一个数字2,输出,总的表达式为 9 3 1 -3 * +10 2 10.因为已经到最后,所以将栈中符号全部出栈并输出。最终输出的后缀表达式结果为 9 3 1 -3 * +10 2 /+