一.栈的基本概念
定义:
限定只能在表的一端(栈顶)进行插入和删除运算的线性表。表尾(an端)称为栈顶,表头(a1端)称为栈底
表达式S=(a1,a2,...,an)
入栈:插入元素到栈顶
出栈:从栈顶删除最后一个元素
运算规则:先进后出 LIFO
存储结构:顺序栈,链栈
逻辑结构:一对一
已知入栈序列,入栈过程中允许出栈,判断可能的出栈序列
入栈序列为...,aj,...,ak,...ai,...,那么一定不存在这样的出栈序列:...ai,...aj,...ak,...。
比如有输入序列1,2 ,3 。那么不可能出现出栈序列为3,1,2。
c,a,b,d 出栈序列不会有c,d,a,b或者b,c,d,a;
二.顺序栈的表示与实现
顺序栈存储结构: 与顺序表十分相似
利用一组地址连续的存储单元依次存放从栈底到栈顶(地址从小到大)的数据元素,同时设置指针top指向栈顶元素的位置。栈底指针base始终指向栈底元素,top初值指向栈底,元素入栈时始终指向栈顶元素的下一个位置(我的理解:top=NULL,所以下面写到的出栈操作要先减再弹)。
【注】
1.栈空标志:base==top;栈满标志:top-base==MaxSize;栈不存在:base=NULL;
2.入栈口诀:top先压后加 S[top++]=an 赋值完成后指针移动
出栈口诀:先减后弹 e=S[--top] (一定要预设top)
代码实现
/*顺序栈*/
#include "stdio.h"
#include "stdlib.h"
#include "malloc.h"
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
typedef char SElemtype;//以char为例
typedef struct
{
SElemtype *base;//栈底指针。构造之前和销毁之后其值为NULL
SElemtype *top;//栈顶指针
int stacksize;//当前分配的内存空间
}SqStack;
/*初始化*/
void InitStack(SqStack &S)
{
S.base=(SElemtype*)malloc(STACK_INIT_SIZE*sizeof(SElemtype));
if(!S.base) printf("Error");
S.top=S.base;//栈空
S.stacksize=STACK_INIT_SIZE;
}
/*判断是否为空*/
bool StackEmpty(SqStack S)
{
if(S.top==S.base) return 1;
else return 0;
}
/*求长度*/
int StackLength(SqStack S)
{
return S.top-S.base;
}
/*清空栈*/
void ClearStack(SqStack &S)
{
if(S.base) S.top=S.base;//栈存在,则让栈空。实际上是让栈的长度为0,栈中不存在有效数据,而让栈底内存保留,是一种逻辑上的清空,而非物理上的释放内存。需要与销毁区分
}
/*销毁栈*/
void DestoryStack(SqStack &S)
{
if(S.base)
{
free(S.base);//释放才是销毁。S.base动态分配的内存块的首地址,那么整个内存块会被释放
S.stacksize=0;//内存空间为0
S.base=S.top=NULL;
}
}
/*进栈*/
void Push(SqStack &S,SElemtype e)
{
if(S.top-S.base>=S.stacksize)//栈满
{
S.base=(SElemtype*)realloc(S.base,(S.stacksize+STACKINCREMENT)*sizeof(SElemtype));
if(!S.base) printf("Error");//分配失败
S.top=S.base+S.stacksize;//注意:新增的空间并不是放在栈的某个位置,栈的内存块被重新分配和扩展(这是一个新的,更大的内存块)。
//新内存块的起始地址更新为S.base,所以S.top相应也要更改。此时应该是刚好栈满,标志就是S.top-S.base=S.stacksize,所以top的值就有了
S.stacksize+=STACKINCREMENT;
}
*S.top++=e;//先压后加
}
/*出栈*/
void Pop(SqStack &S,SElemtype &e)
{
if(S.top==S.base)//栈空
printf("Error");
e=*--S.top;//返回到e.先减后出
}
/*取栈顶元素*/
SElemtype GetTop(SqStack S,SElemtype &e)
{
if(S.top==S.base) printf("Error");//栈空
e=*(S.top-1);//!!!!减1。top指向栈顶元素上方一个
return e;
}
int main()
{
SqStack S;
SElemtype e;
InitStack(S);
printf("栈是否为空:%d",StackEmpty(S));
Push(S,'B');
Push(S,'a');
Push(S,'n');
Push(S,'a');
Push(S,'n');
Push(S,'a');
printf("\n");
while(!StackEmpty(S))
{
printf("%c",GetTop(S,e));
Pop(S,e);
}
}
结果:
最终逆序输出,符合先进后出
【习题】:.在一个具有n个单元的顺序栈中,假设以地址高端作为栈底,以top作为栈顶指针,则当作进栈处理时,top 的变化为 top--。分析top移动的方向:向下。(从上到下 地址从高到低)
三.链栈的表示与实现
与单链表也很相似
代码实现
/*链栈*/
#include "stdio.h"
#include "stdlib.h"
#include "malloc.h"
typedef int SElemtype;
typedef struct StackNode
{
SElemtype data;
struct StackNode *next;
}StackNode,*LinkStack;//结点类型
/*初始化*/
void InitStack(LinkStack &S)
{
S=NULL;
}
/*判断是否为空*/
int StackEmpty(LinkStack S)
{
if(S==NULL) return 1;//注意S是栈顶结点,只有它不存在,链栈才为空。而s->next为空,只能说明栈顶结点没有后续节点
else return 0;
}
/*进栈*/
void Push(LinkStack &S,SElemtype e)
{
LinkStack p=(LinkStack)malloc(sizeof(StackNode));//生成新结点p
if(!p) printf("Error");//失败
p->data=e;//数据域
p->next=S;//p的next指向S
S=p;//p成为栈顶结点
}
/*出栈*/
void Pop(LinkStack &S,SElemtype &e)
{
if(S==NULL) printf("Error");
e=S->data;
LinkStack p=S;
S=S->next;
free(p);
}
/*取栈顶元素*/
SElemtype GetTop(LinkStack S)
{
if(S==NULL) return 0;
else return S->data;
}
int main()
{
LinkStack S;
InitStack(S);
SElemtype e;
Push(S,1);Push(S,2);Push(S,3);Push(S,6);Push(S,7);
Pop(S,e);
printf("popped:%d ",e);
while(StackEmpty(S)==0)
{
printf("top:%d ",GetTop(S));
Pop(S,e);
}
}
四.双栈共享一个栈空间
将编号为0和1的两个栈存放于一个数组空间V[m]中,栈底分别处于数组的两端。当第0号栈的栈顶指针top[0]等于-1时该栈为空,当第1号栈的栈顶指针top[1]等于m时该栈为空。两个栈均从两端向中间增长
优点:互相调剂,灵活性强,减少溢出机会
【注】1.栈空:top[i]==bot[i] i 表示栈编号;栈满:top[0]+1==top[1] 两个栈顶指针相遇
关于双栈中的指针:
top[i],bot[i]是整型变量,记录的是数组的位置索引,比如top[0]=2表示栈0的栈顶元素在V[2]。将他们称为指针是因为功能类似与指针----标记位置并动态移动。
为什么用下标而非真实指针?
数组的特性:数组通过下标直接访问元素,无需像链表那样存储地址。
操作简化: `top[0]++` 等价于“指针右移”。 `top[1]--` 等价于“指针左移”。
代码实现
#include "stdio.h"
#include "stdlib.h"
typedef int Status;
typedef int SElemtype;
//双栈共享空间
typedef struct
{
int top[2],bot[2];//栈顶栈底指针
SElemtype *V;//栈数组:同时存储“两个栈”的元素
int maxsize;//栈最大可容纳元素数
}DblStack;
/*初始化一个大小为m的双向栈S*/
Status Init_Stack(DblStack &S,int m)
{
S.V=(SElemtype*)malloc(m*sizeof(SElemtype));//!!!
if(!S.V) return 0;
S.maxsize=m;//最大能容纳的元素个数
S.top[0]=S.bot[0]=-1;//栈0.此时栈空
S.top[1]=S.bot[1]=m;//栈1
return 1;
}
/*把x插入栈i*/
Status Dblpush(DblStack &S,int i,SElemtype x)
{
if(S.top[1]-S.top[0]==1)
printf("栈满,无法入栈\n");
switch (i)
{
case 0:S.V[++S.top[0]]=x;break;//左栈(0)右栈(1)
case 1:S.V[--S.top[1]]=x;break;//1.给栈数组赋值S.V[]=x;2.确定要赋值的位置:改变S.top[i];3.将位置带入栈数组
default:break;
}
return 1;
}
/*退掉位于栈i栈顶的元素*/
Status Dblpop(DblStack &S,int i,SElemtype &x)
{
if(S.top[i]==S.bot[i])
{
printf("栈空,无数据元素出栈");
}
switch (i)
{
case 0:x=S.V[S.top[0]--];break;
case 1:x=S.V[S.top[1]--];break;
default:printf("无效编号");break;
}
return 1;
}
/*判断栈i空否,空返回1,否则返回0*/
int IsEmpty(DblStack &S,int i)
{
return (S.top[i]==S.bot[i]);
}
/*栈满*/
int IsFull(DblStack S)
{
if(S.top[0]+1==S.top[1])
return 1;
else return 0;
}
int main()
{
DblStack DS;
SElemtype x;
Init_Stack(DS,6);
Dblpush(DS,0,1);
Dblpush(DS,0,7);
Dblpush(DS,1,8);
Dblpush(DS,0,6);
Dblpush(DS,1,3);
while(!IsEmpty(DS,0))
{
Dblpop(DS,0,x);
printf("栈0弹出%d ",x);
}
}
欢迎指正评价:)))