栈的定义
浏览网页时的后退键,word、photoshop等文档或图像编辑软件的撤销(undo)操作,都是使用栈来实现的。
栈是限定仅在标尾进行插入和删除操作的线性表。
允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构
栈的插入操作,叫做进栈,也成压栈、入栈。
栈的删除操作,叫做出栈,有的也叫作弹栈。
进栈出栈的变化形式
出栈只需要保证是栈顶元素就可以
栈的抽象数据类型
1.建立栈。
2.销毁栈(若存在)。
3.清空栈。
4.若栈为空返回true,否则返回false。
5.若存在且非空,返回栈顶元素。
6.若存在,插入新元素到栈中并成为栈顶元素。
7.删除栈中栈顶元素,并用e返回其值。
8.返回栈的元素个数。
栈的顺序存储结构及实现
栈的顺序存储结构
栈的顺序存储结构是线性表顺序存储的简化,称为顺序栈。
/* 顺序栈结构 */
typedef struct
{
SElemType data[MAXSIZE];
int top; /* 用于栈顶指针 */
}SqStack;
栈中有一个元素top=0 有两个元素top=1 空栈top=-1
栈的顺序存储结构——进栈操作
/* 插入元素e为新的栈顶元素 */
Status Push(SqStack *S,SElemType e)
{
if(S->top == MAXSIZE -1) /* 栈满 */
{
return ERROR;
}
S->top++; /* 栈顶指针增加一 */
S->data[S->top]=e; /* 将新插入元素赋值给栈顶空间 */
return OK;
}
栈的顺序存储结构——出栈操作
/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(SqStack *S,SElemType *e)
{
if(S->top==-1)
return ERROR;
*e=S->data[S->top]; /* 将要删除的栈顶元素赋值给e */
S->top--; /* 栈顶指针减一 */
return OK;
}
两栈共享空间
/* 两栈共享空间结构 */
typedef struct
{
SElemType data[MAXSIZE];
int top1; /* 栈1栈顶指针 */
int top2; /* 栈2栈顶指针 */
}SqDoubleStack;
插入元素时,除了要插入元素值参数外,还需要有一个判断时栈还是栈2的栈号参数stackNumber。
/* 插入元素e为新的栈顶元素 */
Status Push(SqDoubleStack *S,SElemType e,int stackNumber)
{
if (S->top1+1==S->top2) /* 栈已满,不能再push新元素了 */
return ERROR;
if (stackNumber==1) /* 栈1有元素进栈 */
S->data[++S->top1]=e; /* 若是栈1则先top1+1后给数组元素赋值。 */
else if (stackNumber==2) /* 栈2有元素进栈 */
S->data[--S->top2]=e; /* 若是栈2则先top2-1后给数组元素赋值。 */
return OK;
}
开头判断栈是否已满
删除元素时,参数只需要是stackNumber
/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(SqDoubleStack *S,SElemType *e,int stackNumber)
{
if (stackNumber==1)
{
if (S->top1==-1)
return ERROR; /* 说明栈1已经是空栈,溢出 */
*e=S->data[S->top1--]; /* 将栈1的栈顶元素出栈 */
}
else if (stackNumber==2)
{
if (S->top2==MAXSIZE)
return ERROR; /* 说明栈2已经是空栈,溢出 */
*e=S->data[S->top2++]; /* 将栈2的栈顶元素出栈 */
}
return OK;
}
栈的链式存储结构及实现
栈的链式存储结构
栈的链式存储结构,简称为链栈。
栈顶放在单链表的头部。并且头结点是不需要的。
对于链栈来说,基本不存在栈满的情况。
对于空栈来说,链表元定义是头指针指向空,那么链栈的空其实就是top=NULL的时候。
/* 链栈结构 */
typedef struct StackNode
{
SElemType data;
struct StackNode *next;
}StackNode,*LinkStackPtr;
typedef struct
{
LinkStackPtr top;
int count;
}LinkStack;
栈的操作绝大部分都和单链表相似,只是在插入和删除上,特殊一些。
栈的链式存储结构——进栈操作
/* 插入元素e为新的栈顶元素 */
Status Push(LinkStack *S,SElemType e)
{
LinkStackPtr s=(LinkStackPtr)malloc(sizeof(StackNode));
s->data=e;
s->next=S->top; /* 把当前的栈顶元素赋值给新结点的直接后继,见图中① */
S->top=s; /* 将新的结点s赋值给栈顶指针,见图中② */
S->count++;
return OK;
}
栈的链式存储结构——出栈操作
/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(LinkStack *S,SElemType *e)
{
LinkStackPtr p;
if(StackEmpty(*S))
return ERROR;
*e=S->top->data;
p=S->top; /* 将栈顶结点赋值给p,见图中③ */
S->top=S->top->next; /* 使得栈顶指针下移一位,指向后一结点,见图中④ */
free(p); /* 释放结点p */
S->count--;
return OK;
}
如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,那么最好是用链栈,反之,如果它的变化在可控范围内,建议使用顺序栈会更好一些。
栈的作用
栈的引入简化了程序设计的问题,划分了不同关注层次,使得思考范围缩小,更加聚焦与我们要解决的问题核心。
栈的应用——递归
斐波那契数列的实现
常规 迭代法
int i;
int a[40];
printf("迭代显示斐波那契数列:\n");
a[0]=0;
a[1]=1;
printf("%d ",a[0]);
printf("%d ",a[1]);
for(i = 2;i < 40;i++)
{
a[i] = a[i-1] + a[i-2];
printf("%d ",a[i]);
}
递归法
int Fbi(int i)
{
if( i < 2 )
return i == 0 ? 0 : 1;
return Fbi(i - 1) + Fbi(i - 2); /* 这里Fbi就是函数自己,等于在调用自己 */
}
{
printf("递归显示斐波那契数列:\n");
for(i = 0;i < 40;i++)
printf("%d ", Fbi(i));
return 0;
}
递归的定义
我们把一个直接调用自己或通过一系列调用语句间接地调用自己的函数,称为递归函数。
每个递归定义必须至少有一个条件,满足时递归不再进行,即不再引用自身而是返回值退出。
在递归的前行和退回阶段,对于每一层递归,函数的局部变量、参数值和返回地址都被压入栈中,后来再被依次弹出,用于返回调用层次中执行代码的其余部分。
栈的应用——四则运算表达式求值
后缀(逆波兰)表示法的定义
所有的运算符号都需要在运算数字的后面出现
中缀表达式转后缀表达式(书P92)