栈的定义和特点
栈是一个特殊的线性表,是限定在一端进行插入和删除操作的线性表;
插入到栈顶称作入栈(PUSH)
从栈顶删除最后一个元素称作出栈 (POP)
图1 栈的表示
假设三个元素a,b,c入栈顺序为a,b,c,那么出栈顺序有几种?
一般表示一个栈都是竖着列表表示 | ||||
栈顶
| ||||
表尾 表头 | ||||
出栈可以等a,b,c都存放进去在依次出栈,出栈结果为:
a |
b |
c |
也可以放完a,然后a出栈,b和c都入栈后再依次出栈,结果为:
b |
c |
a |
当然也可以a,b先入栈,然后依次出栈,最后c入栈,结果为:
c |
a |
b |
还有几种情况,就不一一举例了
那么会出现b,c,a的情况吗?显然是不可能的,因为栈的特点就是只能在栈顶进行入栈和出栈操作,以上这个例子可以很好反应栈的这个特点。
顺序栈的表示和实现
与链表相同栈的存储方式也分为两种:顺序存储结构和链式存储结构
顺序栈的特点:简单,方便,但容易溢出
顺序栈的存储方式:同一般线性表的顺序存储结构完全相同
用一组连续的存储单元依次顺序放置,从栈底到栈顶的数据元素,栈底一般存放在低地址端
几个重要概念:
//栈满:top-base==stacksize;
/*栈满处理方法:
报错返回系统
分配更大的存储空间*/
//空栈:base=top
//上溢:栈已满,又要压入元素 (通常是一种错误)
//下溢:栈已空,又要弹出元素 (通常被用作结束条件)
顺序栈的表示
base表示指向栈底的指针
top表示指向栈顶的指针
//顺序栈的表示
#define MAXSIZE 100;
typedef struct{
SElemType *base; //栈底
SElemType *top; //栈顶
int stacksize; //最大容量
}SqStack;
顺序栈的初始化
相当于建立一个空栈即top=base,两个指针指向相同
//顺序栈的初始化
Status InitStack(SqStack S){
S.base=(SElemType *)malloc(sizeof(SElemType)*MAXSIZE);//分配存储空间
if(!S.base){
printf("OVERLOW");
}
else{
S.top=S.base; //顶指针和底指针指向相同,相当于建立一个空栈
S.stacksize=MAXSIZE;
return OK;
}
}
判断顺序栈是否为空
空栈的标志性条件top=base,两个指针有相同的指向
如果是空栈返回TURE,如果不是空栈返回FALSE
//判断顺序栈是否为空
Status StackEmpty(SqStack S){
if(S.top==S.base){ //判断栈为空的标志base=top;
teturn TURE;
}
else{
return FALSE;
}
}
求顺序栈的长度
这里直接用两个指针相减即可
//求顺序栈的长度
int StackLength(SqStack S){
return S.top-S.base; //两个指针相减可以算出中间隔了多少个数据元素,也就是栈的长度
}
清空顺序栈
这里与清空单链表类似
//清空顺序栈
/*与链表相同,就是把该表置空,但保留表的头结点,栈里的top指针就相当于链表里的头结点*/
Status ClearStack(SqStack S){
if(S.base){
S.base=S.top; //相当与把当前栈变成一个空栈
}
}
看一下单链表的清空
/*清空单链表*/
Status ClearList(LinkList &L){
Lnode *p,*q;
p=L->next; //p指向首元结点
while(p){
q=p->next; //q指向p后面的结点
free(p); //释放p
p=q; //p,q指向同一结点
}
L->next=NULL;
return OK;
}
销毁顺序栈
与销毁单链表类似
//销毁顺序栈
Status DestoryStack(SqStack S){
if(S.base){
S.stacksize=0; //表长为空
S.base=S.top=NULL; //指针为空
}
return OK;
}
销毁单链表
/*销毁单链表(头结点,头指针都不存在)*/
Status DestoryList_L(LinkList &L){ //L为指向头结点的指针
Lnode * p; //或者LinkList p;
while(L){
p=L; //p和L指向同一个结点
L=L->next; //L向后移动一个
free(p); //释放p
}
return OK;
}
顺序栈的入栈(注意:栈只能在栈顶进行入栈和出栈操作)
具体步骤
/*判断是否栈满
将元素e压入栈顶
栈顶指针加一*/
这里注意栈满的条件:S.top-S.base==stacksize
//顺序栈的入栈
/*判断是否栈满
将元素e压入栈顶
栈顶指针加一*/
Status Push(SqStack S,SElemType e){
if(S.top-S.base==stacksize){ //这里的top指针指向的是栈顶元素的下一个位置
return ERROR; //栈满
}
*S.top++=e;
return OK;
}
*S.top++=e;
这一句其实是先将数据元素e放入栈中,然后栈顶指针top移向下一个位置。
如果top指针指向当前表的最后一个数据元素(也就是栈顶元素),即表尾,那么要先将top指针向后移动一位,然后再进行赋值,该语句就变为*++S.top=e;
顺序栈的出栈(注意:栈只能在栈顶进行入栈和出栈操作)
具体步骤
/*判断是否栈空
获取栈顶元素e
栈顶指针减一*/
这里的top指针同样也是指向栈顶元素的下一个位置
//顺序栈的出栈
/*判断是否栈空
获取栈顶元素e
栈顶指针减一*/
Status Pop(SqStack S,SElemType e){
if(S.top==S.base){
return ERROR;
}
e=*--S.top;
}
e=*--S.top;
这一句是先将top指针向下移动一个位置,然后将该位置的数据元素(也就是栈顶元素)赋值给e,
就可以完成出栈。
如果top指针指向当前表的最后一个数据元素(也就是栈顶元素),即表尾,那么就可以先直接将S.top赋值给e,然后将top指针向下移动一个位置,即e=*S.top--;
链栈的表示和实现
链栈是运算受限的单链表,只能在表头进行插入删除操作
与单链表相比较链栈的插入和删除操作就很简单了,相当于只表头进行
图2 链栈的表示
链栈与链表的比较
/*链表的头结点就是栈顶指针top
链栈里不需要头结点
链栈基本不存在栈满情况
空栈相当于头指针指向为空
插入和删除操作仅存在于栈顶*/
链栈的表示
与单链表的表示很像
//链栈的表示
typedef struct StackNode{
SElemType data;
struct StackNode *next;
}StackNode,*LinkStack;
链栈的初始化
相当于建立空栈
//链栈的初始化
void InitStack(LinkStack S){
S=NULL; //相当与头结点
return OK;
}
判断链栈是否为空
如果是空栈返回TURE,如果不是空栈返回FALSE
//判断链栈是否为空
Status StackEmpty(LinkStack S){
if(S==NULL){
return TRUE;
}
else{
return FALSE;
}
}
链栈的入栈
相当于在S的前面插入新的数据元素,新的数据元素成为栈顶元素
新的数据元素的next域指向S
//链栈的入栈
Status Push(LinkStack S,SElemType e){ //S指向栈顶元素
p=(StackNode *)malloc(sizeof(StackNode)); //分配存储空间
p->data=e;
p->next=S;
S=p;
return OK;
}
链栈的出栈
出栈只需要先保存top指针的内容,然后向下移动一个位置即可
//链栈的出栈
Status Pop(LinkStack S,SElemType e){
if(S==NULL){
return ERROR; //与顺序栈一样要先判断栈是否为空
}
else{
e=S->data;
p=S;
S=S->next;
free(p);
return OK;
}
}
取出栈顶元素
直接保存top指针的内容即可
//取出栈顶元素
SElemType GetTop(LinkStack S){
if(S!=NULL){
return S->data;
}
}