-----------栈的基本概念
1. 栈的定义
栈:只允许在一端进行插入或删除操作的线性表。首先栈是一种线性表,但是限定这种线性表只能在
一端进行插入和删除操作。如下图:
栈顶:线性表允许进行插入和删除的那一端。
栈底:固定的,不允许进行插入和删除的另一端。
空栈:不含任何元素的空表。
假设某个栈 S = ( a1 , a2 , a3 , a4 , a5 ) ,如上图,则 a1 为栈底元素,a5 为栈顶元素。由于栈
只能在栈顶进行插入和删除操作,故进栈依次为 a1 , a2 , a3 , a4 , a5 ,而出栈次序为 a5 , a4 , a3 ,
a2 , a1 。由此可见,栈的一个明显的操作特性可以概括为后进先出(Last In First Out , LIFO),
故又称为后进先出的线性表。
注意:我们每接触一种新的数据结构类型,都应该分别从其逻辑结构、存储结构和对数据的运算
三方面着手,以加深对定义的理解。
2. 栈的基本操作
InitStack(&S) : 初始化一个空栈 S
StackEmpty(S) : 判断一个栈是否为空,若栈 S 为空返回 true ,否则返回 false
Push( &S , x ) : 进栈,若 栈 S 未满,将 x 加入使之成为新栈顶。
Pop( &S , &x) : 出栈,若栈 S 非空,弹出栈顶元素,并用 x 返回。
GetTop( S , &x ) : 读栈顶元素,若栈 S 非空,用 x 返回栈顶元素。
ClearStack( &S ) :销毁栈,并释放栈 S 占用的存储空间。
(注:符号 “ &” 是 C++ 特有的,用来表示引用,有的书上采用 C 语言中的指针类型 “ * ” ,也可以
达到传址的目的。)
-----------栈的顺序存储结构
1. 顺序栈的实现
栈的顺序存储称为顺序栈,它是利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时
附设一个指针(top) 指示当前栈顶的位置。
------------------------------------------------------------------------------------------------------------
栈的顺序存储类型可描述为:
#define MaxSize 50 // 定义栈中元素的最大个数
typedef struct {
Elemtype data[ MaxSize ] ; // 存放栈中元素
int top ; // 栈顶指针
} SqStack ;
---------------------------------------------------------------------------------------------------------------
栈顶指针:S.top , 初始时设置为 S.top = -1 ;
栈顶元素: S.data[ S.top ]
进栈操作: 栈不满时,栈顶指针先加 1 ,再送值到栈顶元素
出栈操作: 栈非空时,先取栈顶元素值, 再将栈顶指针域减 1
栈空条件: S.top = -1
栈满条件: S.top = MaxSize -1
栈长: S.top + 1
由于顺序栈的入栈操作受数组上届的约束,当对栈的最大使用空间估计不足时,有可能发生
栈上溢出,此时应及时向用户报告消息,以便及时处理,避免出错。
对于栈和后面提到的队列的判空和判满条件会因为实际给的条件不同而变化,以上得到的方法
以及下面给出的代码实现只是在栈顶指针设定的条件下相应的方法,而其他情况需要具体问题具体
分析。
2. 顺序栈的基本操作
栈操作的示意图如上图 “ 栈顶指针和栈中元素之间的关系 ” (a) 是空栈,(c) 是A, B,C , D ,E
共 5 个元素依次入栈后的结果; (d) 是在(c)之后 E , D , C 相继出栈,此时栈中还有 2 个元素,或
许最近出栈的元素 C , D , E 仍在原先的单元存储着,但 top 指针已经指向了新的栈顶,则 C , D ,E
已不在栈中了,读者应通过示意图深刻理解栈顶指针的作用。
下面是顺序栈上常用的基本运算的实现。
(1)初始化
--------------------------------------------------------------------------------------------------------------------------------------
void InitStack( &s) {
s.top = -1 ; // 初始化栈顶指针
}
-------------------------------------------------------------------------------------------------------------------------------------
(2)判栈空
--------------------------------------------------------------------------------------------------------------------------------------
bool StackEmpty(s){
if( s.top = -1 ){ // 栈空
return true;
}
else{ // 不空
return false;
}
}
---------------------------------------------------------------------------------------------------------------------------------------
(3)进栈
--------------------------------------------------------------------------------------------------------------------------------------
bool Push( SqStack &s , ElemType x){
if ( s.top == MaxSize -1 ){ // 栈满,报错
return false;
}
s.data[++s.top] = x ; // 指针先加1 , 然后再入栈
return true ;
}
--------------------------------------------------------------------------------------------------------------------------------------
(4) 出栈
--------------------------------------------------------------------------------------------------------------------------------------
bool Pop( SqStack &s , ElemType &x){
if ( s.top == -1 ){ // 栈满,报错
return false;
}
x = s.data[ s.top - -] ; // 先出栈,然后指针再减 1
retrun true ;
}
-----------------------------------------------------------------------------------------------------------------------------------
(5)读栈顶元素
----------------------------------------------------------------------------------------------------------------------------------
bool GetTop( SqStack s , ElemType &x){
if ( s.top == -1 ){ // 栈空,报错
return false ;
}
x = s.data[ s.top ] ; // x 记录栈顶元素
return true ;
}
--------------------------------------------------------------------------------------------------------------------------------
注意:这里的栈顶指针指向的就是栈顶元素,所以进栈时的操作是 s.data[++s.top] = x ;
出栈时的操作是 x = s.data[ s.top - -] ;
如果栈顶指针初始化为 s.top = 0 , 即栈顶指针指向栈顶元素的下一个位置,则入栈操作
变为 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 ; // 栈类型定义
-------------------------------------------------------------------------------------------------------------------------------
采用链式存储,便于结点的插入和删除。链栈的操作与链表类似。读者需要注意的是,
对于带头结点和不带头结点的链栈,在具体的实现方面有所不同。