一、栈的类型定义
栈(Stack)是限定仅在表位进行插入或删除操作的线性表. 因此, 对栈来说, 表尾端有特殊含义, 称为栈顶(top), 相应地, 表头端称为栈底(bottom).
栈是后进先出(Last In First Out)的线性表(简称LIFO结构),栈的特点可以用铁路调度站形象地表示.在程序设计中, 如果需要安装保存数据时相反的顺序来使用数据, 则可以利用栈来实现.
栈的基本操作除了在栈顶进行插入或删除外, 还有栈的初始化、栈空的判定, 以及取栈顶元素等. 下面给出栈的抽象数据类型定义:
ADT Stack
{
数据对象:D={a(i)|a(i)∈ElemSet, i=1, 2, ..., n, n>=0}
数据关系:R={<a(i-1),a(i)>|a(i-1), a(i), a(i)∈D, i=2, ... , n}
约定a(n)端为栈顶, a(1)端为栈顶.
基本操作:
InitStack(&S)
操作结果:构造一个空栈S.
DestroyStack(&S)
初始条件:栈S已存在.
操作结果:栈S被销毁.
ClearStack(&S)
初始条件:栈S已存在.
操作结果:栈S清为空栈.
StackEmpty(S)
初始条件:栈S已存在.
操作结果:栈S为空栈, 则返回true, 否则返回false.
StackLenght(S)
初始条件:栈S已存在.
操作结果:返回S的元素个数,即栈的长度.
GetTop(S,&e)
初始条件:栈S已存在并且非空.
操作结果:用e返回S的栈顶元素.
Push(&S,e)
初始条件:栈S已存在.
操作结果:插入元素e为新的栈顶元素.
Pop(&S,&e)
初始条件:栈S已存在并且非空.
操作结果:删除S的栈顶元素, 并用e返回其值.
StackTraverse(S)
初始条件:栈S已存在并且非空.
操作结果:从栈底到栈顶依次对S的每个数据元素进行访问.
}ADT Stack
栈有两种存储表示方法, 分别称为顺序栈和链栈.
二、顺序栈的出现
顺序栈是利用顺序存储结构实现的栈,即利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时附设指针top指示栈顶元素在顺序栈中的位置.
通常习惯的做法是:以top=0表示空栈,鉴于C语言数组中数组下标约定从0开始,则当以C作为描述语言时,如此设定会带来很大不便,因此另设指针base表示栈底元素 在顺序栈中的位置,当top和base的值相等时,表示空栈.定义如下:#define MAXSIZE 100
typedef struct
{
SElemType *base;
SElemType *top;
int stacksize;
} sqStack;
其中stacksize指示栈的最大容量;base为栈底指针,top为栈顶指针.每当插入新栈顶元素时,指针top增1;删除栈顶元素时,指针top减1.因此非空栈中的栈顶指针始终在栈顶元素的下一个位置上.
1.初始化顺序栈的初始化操作就是为顺序栈动态分配一个预定义大小的数组空间,base指向栈底,并使top的初值也指向栈底,表示栈为空,stacksize置为栈的最大容量.
顺序栈的初始化[算法描述]
Status InitStack(Sqstack &S)
{
S.base=new SElemType[MAXSIZE]; /*分配一个最大容量为MAXSIZE的数组空间*/
if(!S.base) /*存储分配失败*/
{
exit(OVERFLOW);
}
S.top=S.base;
s.stacksize=MAXSIZE;
return OK;
}
2.入栈
入栈操作是指在栈顶插入一个新的元素.首先判断栈是否满,若满则报错,否则将新元素压入栈顶,栈顶指针加1.
顺序栈的入栈
[算法描述]
Status Push(Sqstack &S,SElemType e)
{
/*插入元素e为新的栈顶元素*/
if(S.top-S.base == S.stacksize) /*栈满*/
{
return ERROR;
}
*S.top++=e; /*元素e压入栈顶,栈顶指针加1*/
return OK;
}
3.出栈
出栈操作是将栈顶元素删除.首先判断栈是否为空.若空则报错,否则栈顶指针减1,栈顶元素出栈.
顺序栈的出栈
[算法描述]
Status Pop(sqStack &S,SElemType &e)
{
/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
if(S.top == S.base) /*栈空*/
{
return OK;
}
e=*--S.top; /*栈顶指针减1,将栈顶元素赋给e*/
return OK;
}
4.取栈顶元素
此操作仅取得当前栈顶元素的值,不修改栈顶指针.首先判断栈是否为空,若空则报错,否则通过栈顶指针获取栈顶元素.
取顺序栈的栈顶元素
[算法描述]
SElemType GetTop(Sqstack S)
{
/*若栈不空,返回S的栈顶元素,否则退出*/
if(s.top == s.base)
{
exit(1);
}
return *(S.top-1); /*栈顶指针减1,返回栈顶元素*/
}
<span style="font-family: Arial, Helvetica, sans-serif; font-size: 18px; background-color: rgb(255, 255, 255);">由于顺序栈和顺序表一样,受到最大空间容量的限制,虽然可以在"满员"时重新分配空间扩大容量,但工作量较大,应该</span><span style="font-family: Arial, Helvetica, sans-serif; font-size: 18px; background-color: rgb(255, 255, 255);">尽量避免.因此,</span><span style="font-family: Arial, Helvetica, sans-serif; font-size: 18px; background-color: rgb(255, 255, 255);">在应用程序无法预先估计栈可能达到的最大容量时,还是应该使用链栈.</span>
三、链栈的表示和实现
链栈是指采用链式存储结构实现的栈.通常链栈用单链表来表示.
链表的结点结构与单链表的结构相同,在此用StackNode表示,定义如下:
typedef struct StackNode
{
SElemType data;
struct StackNode *next;
} StackNode, *LinkStack;
由于栈的主要操作是在栈顶插入和删除,显然以链表的头部作为栈顶是最方便的,而且没必要像单链表那样为了操作方便附加一个头结点.
下面给出链栈部分操作的实现.
1.初始化
链栈的初始化操作就是构造一个空栈,因为没必要设头结点,所以直接将栈顶指针置空即可.
链栈的初始化
[算法描述]
Status InitStack(LinkStack &S)
{
/*构造一个空栈S,栈顶指针置空*/
S=NULL;
return OK;
}
2.入栈
首先为新的栈顶元素分配结点空间,将新结点插入到栈顶,然后修改栈顶指针.
链栈的入栈
[算法描述]
Status Push(LinkStack &S,SElemType e)
{
p=new StackNode;
p->data=e;
p->next=S; /*将新结点p插入栈顶*/
S=p; /*修改栈顶指针*/
return OK;
}
3.出栈
首先判断栈是否为空,若空则报错,否则获取栈顶元素,然后修改栈顶指针,释放原栈顶元素的空间.
链栈的出栈
[算法描述]
Status Pop(LinkStack &S,SElemType &e)
{
/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK; 否则返回ERROR*/
if(S == NULL)
{
return ERROR;
}
e=S->data;
p=S;S=S->next; /*修改栈顶指针*/
delete p; /*释放原栈顶结点空间*/
return OK;
}