目录
一、栈的基本概念
栈是只允许在一端进行插入或删除操作的线性表。
栈顶:线性表允许进入插入删除的那一端。
栈底:固定的,不允许进入插入和删除的另一端。
空栈:不含任何元素的空表。
假设某个栈如图所示,a1为栈底元素,a5为栈顶元素。栈只能在栈顶进行插入和删除操作,进栈次序依次为a1,a2,a3,a4,a5,而出栈次序为a5,a4,a3,a2,a1。由此可见,栈的操作特性可以明显地概括为后进先出。
每接触一种数据结构,都应从其逻辑结构、存储结构和运算三个方面着手
二、顺序栈
1、顺序栈的定义
采用顺序存储的栈称为顺序栈,它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针(top)指示当前栈顶元素的位置。栈的顺序存储类型可描述为
#define MaxSize 50 //定义栈中元素的最大个数
typedef struct{
Elemtype data[MaxSize]; //存放栈中元素
int top; //栈顶指针
}SqStack;
2、顺序栈的基本操作
(1)初始化
初始时设置S.top=-1;栈顶元素:S.data[S.top]。
void InitStack(SqStack &S){
S.top=-1; //初始化栈顶指针
}
(2)判栈空
bool StackEmpty(SqStack S){
if(S.top==-1)
return true; //栈空
else
return false; //不空
}
(3)进栈
栈不满时,栈顶指针先加1,再送值到栈顶。
(4)出栈
栈非空时,先取栈顶元素,再将栈顶指针减1。
bool Pop(SqStack &S,ElemType &x){
if(S.top==-1) //栈空,报错
return false;
x=S.data[S.top--]; //先出栈,指针再减1
return true;
}
(5)读栈顶元素
仅为读取栈顶元素,并没有出栈操作,因此原栈顶元素依然保留在栈中。
bool GetTop(SqStack S,ElemType &x){
if(S.top==-1) //栈空,报错
return false;
x=S.data[S.top]; //x记录栈顶元素
return true;
}
这里的top指的是栈顶元素。于是,进栈操作为S.data[++S.top]=x,出栈操作为x=S.data[S.top--]。若栈顶指针初始化为S.top=0,即top指向栈顶元素的下一位置,则入栈操作变为S.data[S.top++]=x;出栈操作变为x=S.data[--S.top]。相应的栈空和栈满条件也会发生变化。
顺序栈的入栈操作受数组上界的约束,当对栈的最大使用空间估计不足时,有可能发生栈上溢,而分配较大的空间容易造成浪费。
3、共享栈
利用栈底位置相对不变的特性,可让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸。
两个栈的栈顶指针都指向栈顶元素,top0=-1时0号栈为空,top1=MaxSize时1号栈为空;仅当两个栈顶指针相邻时(top1-top0=1)时,判断为栈满。当0号栈进栈时,top0先加1再赋值,1号栈进栈时,top1先减1再赋值;出栈时则刚好相反。
共享栈是为了更有效地利用存储空间,两个栈的空间相互调节,只有在整个存储空间被占满时才发生上溢。其存储数据的时间复杂度均为O(1),所以对存取效率没有什么影响。
三、链栈
采用链式存储的栈称为链栈,链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。通常采用单链表实现,链头作为栈顶,并规定所有的操作都是在单链表的表头进行的。这里规定链栈没有头结点,Lhead指向栈顶元素,如图所示。
栈的链式存储类型可描述为
typedef struct Linknode{
ElemType data; //数据域
struct Linknode *next; //指针域
}LiStack; //栈类型定义
采用链式存储,便于结点的插入和删除。链栈的操作与链表类似,入栈和出栈的操作都在链表的表头进行。需要注意的是,对于带头结点和不带头结点(推荐)的链栈,具体实现会有所不同。
设输入序列为1,2,3,则经过栈的作用后可以得到()中不同的输出序列。合法出栈数目==卡特兰数,公式为:
四、栈的应用
1、栈在括号匹配中的应用
2、栈在表达式求值中的应用
中缀转后缀表达式,使用左优先原则:只要左边的运算符能先计算,就优先算左边的。
中缀转前缀表达式,使用右优先原则:只要右边的运算符能先计算,就优先算右边的。
后缀表达式的手算方法:从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应运算,合体为一个操作数。