栈和队列是两种重要的线性结构。从数据结构的角度看,栈和队列也是线性表,其特殊性在于栈和队列的基本操作是线性表操作的子集,它们是操作受限的线性表,因此,可以称它们为限定性的数据结构。但从数据类型角度看,它们是和线性表大不相同的两类重要的抽象数据类型。
1、介绍
栈(stack)是限定仅在表尾进行插入或删除操作的线性表。因此,对于栈来说,表尾端有其特殊含义,称为栈顶(top),相应地,表头端称为栈底(bottom)。不含元素的空表称为空栈。假设栈S=(a1,a2,...,an),则称a1为栈底元素,an为栈顶元素。栈中元素按a1,a2,...,an的次序进栈,退栈的第一个元素应为栈顶元素。也就是说,栈的修改是按后进先出的原则进行的,因此,栈又称为后进先出(last in first out)的线性表(简称LIFO结构)。下面的图1是栈结构的示意图。
图1 栈结构示意图
2、栈的抽象数据类型的定义
ADT Stack{
数据对象:D={ai | ai∈ElemSet, i=1,2, …,n, n≥0}
数据关系:R1={<ai-1,ai> | ai-1,ai∈D, i=1,2, …,n }
约定an端为栈顶,a1端为栈底。
基本操作:
InitStack( &S )
操作结果:构造一个空栈S。
DestroyStack ( &S )
初始条件:栈S已存在。
操作结果:销毁栈S。
ClearStack( &S )
初始条件:栈S已存在。
操作结果:将S清为空栈。
StackEmpty( S )
初始条件:栈S已存在。
操作结果:若S为空栈,则返回TRUE,否则返回FALSE。
StackLength( S )
初始条件:栈S已存在。
操作结果:返回S的数据元素个数,即栈的长度。
GetTop( S, &e )
初始条件:栈S已存在且非空。
操作结果:用e返回S的栈顶元素。
Push( &S, e )
初始条件:栈S已存在。
操作结果:插入元素e为新的栈顶元素。
Pop( &S, &e )
初始条件:栈S已存在且非空。
操作结果:删除S的栈顶元素,并用e返回其值。
StackTraverse( S, visit() )
初始条件:栈S已存在且非空。
操作结果:从栈底到栈顶依次对S的每个数据元素调用函数visit()。一旦visit()失败,则操作失败。
}ADT Stack
栈的数据元素类型在应用程序内定义,并称插入元素的操作为入栈,删除元素的操作为出栈。
3、栈的表示和实现
和线性表类似,栈也有两种存储表示方法。
3.1 顺序栈
顺序栈,即栈的顺序存储结构式利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时附设top指针指示栈顶元素在顺序栈中的位置。通常的习惯做法是以top=0表示空栈,鉴于C语言中数组的下标约定从0开始,则当以C做描述语言时,如此设定会带来很大不便;另一方面,由于栈在使用过程中所需最大空间的大小很难估计,因此,一般来说,在初始化设空栈时不应限定栈的最大容量。一个较合理的做法是:先为栈分配一个基本容量,然后再应用过程中,当栈的空间不够时在逐段扩大。为此,可设定两个常量:STACK_INIT_SIZE(存储空间初始分配量)和STACKINCREMENT(存储空间分配增量),并以下述类型作为顺序栈的定义。
typedef struct{
SElemType *base;
SElemType *top;
int stacksize;
}SqStack;
其中,stacksize指示栈的当前可使用的最大容量。栈的初始化操作为:按设定的初始分配量进行第一次存储分配,base可称为栈底指针,在顺序栈中,它始终指向栈底的位置,若base的值为NULL,则表明栈结构不存在。称top为栈顶指针,其初值指向栈底,即top=base可作为占空的标记,每当插入新的栈顶元素时,指针top增1;删除栈顶元素时,指针top减1,因此,非空栈中的栈顶指针始终在栈顶元素的下一个位置上。图2说明了栈中元素和栈顶指针之间的对应关系。
图2 栈中元素和栈顶指针的关系
3.2 链式栈
链式栈表示如图3所示。
图3 链式栈的表示
4、代码
下面是顺序栈的C语言简单实现,其中实现了顺序栈的push,pop,init等简单地操作,编译环境为xp vc6.0,亲测可运行。^_^
#include <stdio.h>
#include <stdlib.h>
//function status code
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
//function type, function return value type, correspond to the function status code
typedef int Status;
//element type of a data element
typedef int SElemType;
//Sequential Stack implement
#define STACK_INIT_SIZE 100//initial size of sequential stack
#define STACKINCREMENT 10//incremental size
typedef struct{
SElemType *base;
SElemType *top;
int stacksize;
}SqStack;
Status InitStack(SqStack &S)
{
//initialize an empty stack
S.base=(SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType));
//allocation failure
if (!S.base)
{
exit(OVERFLOW);
}
S.top=S.base;
S.stacksize=STACK_INIT_SIZE;
return OK;
}
Status GetTop(SqStack S,SElemType &e)
{
//if not empty, storage the top element into e and return OK, or return ERROR
if (S.top==S.base)
{
printf("The stack is empty, so no element can be get by the GetTop function.\n");
return ERROR;
}
e=*(S.top-1);
return OK;
}
Status Push(SqStack &S,SElemType e)
{
//insert element e as the top element
//if stack is full, allocate more space for it
if (S.top-S.base>=S.stacksize)
{
S.base=(SElemType *)realloc(S.base,(S.stacksize+STACKINCREMENT)*sizeof(SElemType));
if (!S.base)
{
printf("Enlarge space for stack failure.\n");
exit(OVERFLOW);
}
S.top=S.base+S.stacksize;
S.stacksize +=STACK_INIT_SIZE;
}
*S.top++ = e;
return OK;
}
Status Pop(SqStack &S,SElemType &e)
{
//if stack is not empty, delete the top element of the stack, storage it into e for external use and return OK, OR,return ERROR;
//stack is empty, return ERROR.
if (S.top==S.base)
{
return ERROR;
}
e=*--S.top;
return OK;
}
int main()
{
//declare a SqStack
SqStack sqstack;
//initialize the sqstack
InitStack(sqstack);
//push element 10-19
int i;
for (i=10;i<20;i++)
{
Push(sqstack,i);
}
printf("10-19 is push into the stack sequentially.\n");
//show top
SElemType temp;
GetTop(sqstack,temp);
printf("The top element of the stack is:%d\n",temp);
//pop
Pop(sqstack,temp);
printf("Element(value=%d) is poped.\n",temp);
//show top again
GetTop(sqstack,temp);
printf("Now, the top element of the stack is:%d\n",temp);
return 0;
}