1、栈的定义:
栈(stack)限定仅在表位进行插入和删除操作的线性表。其特殊性就在于该限制性。栈底固定,在栈顶进行入栈和出栈。
2、栈的顺序存储
构建栈结构如下:包含一个数组data[]和一个栈顶指示标top。数组用来存储数据;top用来标识栈顶位置。
typedef struct
{
SElemType data[MAXSIZE];
int top; //用于栈顶指针
}SqStack;
/************************************************************************/
/* 顺序栈 */
/************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <math.h>
#include <time.h>
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20
typedef int Status;
typedef int SElemType; //stack元素类型
//定义顺序栈结构
typedef struct
{
SElemType data[MAXSIZE];
int top; //用于栈顶指针
}SqStack;
Status visitStack(SElemType c)
{
printf("%d",c);
return OK;
}
//构造一个空栈S
Status InitStack(SqStack *S)
{
S->top=-1;
return OK;
}
//把一个已有栈S清空
Status ClearStack(SqStack *S)
{
S->top=-1;
return OK;
}
//判断栈S是否为空栈。是返回TRUE,否返回FALSE
Status isEmptyStack(SqStack S)
{
if (S.top==-1)
return TRUE;
else
return FALSE;
}
//计算栈S中元素的个数
int StackLength(SqStack S)
{
return S.top+1; //因为元素是从下标0开始存放
}
//若栈不为空,取栈顶元素e,并返回OK;否则返回ERROR
Status GetTop(SqStack S, SElemType *e) //为什么要用指针??——用指针传递参数,外部同步更改
{
if (S.top==-1)
return ERROR;
else
{
*e=S.data[S.top];
return OK;
}
}
//插入元素e为新的栈顶元素
Status Push(SqStack *S, SElemType e)
{
if (S->top ==MAXSIZE-1)
{
printf("栈满\n");
return ERROR;
}
else
{
S->top++;
S->data[S->top]=e;
return OK;
}
}
//若栈不为空,删除栈顶元素,并用e返回其值,返回OK;否则,返回ERROR
Status Pop(SqStack *S, SElemType *e)
{
if (S->top == -1)
return ERROR;
else
{
*e=S->data[S->top];
S->top--;
return OK;
}
}
//从栈底到栈顶显示元素
Status StackTraverse(SqStack S)
{
int i=0;
while(i<=S.top)
{
visitStack(S.data[i]);
i++;
}
printf("\n");
return OK;
}
int main()
{
SqStack s;
int e;
if (InitStack(&s)==OK)
{
for (int j=1;j<=10;j++)
{
Push(&s,j);
}
}
printf("栈中元素依次为:");
StackTraverse(s);
Pop(&s,&e);
printf("弹出栈顶元素e=%d\n",e);
printf("栈顶元素出栈之后,栈中元素依次为:");
StackTraverse(s);
}
3、两栈共享空间
原因是:单个栈处理的时候,数组固定,移动和删除、数组大小等都受限,浪费空间。因此提出一种共享方式。
两个栈对应的两个栈顶,分别处于数组的两端,向中间靠拢。只要top1和top2不见面,那么栈中就有空间可供使用。
结构如下:
//定义两个栈共享空间的结构体
typedef struct
{
SElemType data[MAXSIZE];
int top1; //栈1的栈顶指针
int top2; //栈2的栈顶指针
}SqDoubleStack;
/************************************************************************/
/* 两栈共享空间结构 */
/************************************************************************/
//定义两个栈共享空间的结构体
typedef struct
{
SElemType data[MAXSIZE];
int top1; //栈1的栈顶指针
int top2; //栈2的栈顶指针
}SqDoubleStack;
//初始化共享栈
Status InitStack(SqDoubleStack *S)
{
S->top1=-1;
S->top2=MAXSIZE; //数据存放在0~MAXSIZE-1之间由两端向中间
return OK;
}
//把共享栈置空
Status ClearStack(SqDoubleStack *S)
{
S->top1=-1;
S->top2=MAXSIZE;
return OK;
}
//判断共享栈是否为空栈
Status isEmptyStack(SqDoubleStack S)
{
if (S.top1==-1 && S.top2==MAXSIZE)
return TRUE;
else
return FALSE;
}
//返回S中元素个数:要分别判断栈1和栈2
int StackLength(SqDoubleStack S)
{
int len1=S.top1-(-1);
int len2=MAXSIZE - S.top2;
return len1+len2;
}
//插入e为新栈顶元素
Status Push(SqDoubleStack *S, SElemType e, int stackNumber)
{
if (S->top1==S->top2)
{
printf("栈满\n");
return ERROR;
}
if (stackNumber==1) //给栈1插入
{
S->data[++S->top1] = e;
}
if (stackNumber==2)//给栈2插入
{
S->data[--S->top2] =e;
}
return OK;
}
//遍历共享栈
Status StackTraverse(SqDoubleStack S)
{
int i;
i=0;
while(i<=S.top1)
{
visitStack(S.data[i++]);
}
i=S.top2;
while(i<MAXSIZE)
{
visitStack(S.data[i++]);
}
printf("\n");
return OK;
}
int main()
{
printf("--------------------------------");
SqDoubleStack ss;
int ee;
int i;
if (InitStack(&ss)==OK)
{
for (i=0;i<=5;i++)
{
Push(&ss,i,1);
}
for( i=MAXSIZE;i>=MAXSIZE-3;i--)
{
Push(&ss,i,2);
}
}
int len=StackLength(ss);
printf("len=%d\n",len);
StackTraverse(ss);
getchar();
}
4、栈的链式存储结构(LinkStack)
栈的链式存储结构简称为链栈。
相比于线性表的链式存储结构,链栈①不需要头结点;②让top和头指针合二为一;③插入和删除元素只能在头指针处进行。
定义链表结构如下:
//定义链栈结构
struct StackNode
{
SElemType data;
struct StackNode *next;
};
typedef struct StackNode StackNode;
typedef struct StackNode* LinkStackPtr; //链栈指针
typedef struct
{
LinkStackPtr top;
int count;
}LinkStack;
/************************************************************************/
/* 3、栈的链式存储结构——链栈 */
/************************************************************************/
//定义链栈结构
struct StackNode
{
SElemType data;
struct StackNode *next;
};
typedef struct StackNode StackNode;
typedef struct StackNode* LinkStackPtr; //链栈指针
typedef struct
{
LinkStackPtr top;
int count;
}LinkStack;
//构造一个空的链栈S
Status InitStack(LinkStack *S)
{
S->top=(LinkStackPtr)malloc(sizeof(StackNode));
if (S->top==NULL)
return ERROR;
S->top=NULL;
S->count=0;
return OK;
}
//把S置为空的链栈,要一个一个释放数据
Status ClearStack(LinkStack *S)
{
LinkStackPtr p,q;
p=S->top;
while (p!=NULL)
{
q=p;
p=p->next;
free(q);
}
S->count =0;
return OK;
}
//判断,若栈为空栈,则返回TRUE,否则返回FALSE
Status isStackEmpty(LinkStack S) //因为还存放数据个数,因此可直接通过数据量【判断
{
if (S.count==0)
return TRUE;
else
return FALSE;
}
//返回链栈中的元素个数
int StackLength(LinkStack S)
{
return S.count;
}
//若栈不为空,用e返回S的栈顶元素,并返回OK,否则返回ERROR
Status GetTop(LinkStack S, SElemType *e)
{
if (S.top==NULL)
return ERROR;
else
*e=S.top->data;
return OK;
}
//插入元素e作为新的栈顶元素
Status Push(LinkStack *S, SElemType e)
{
LinkStackPtr m= (LinkStackPtr)malloc(sizeof(StackNode));
m->data=e;
m->next=S->top;
S->top=m;
S->count++;
return OK;
}
//若栈不为空,删除栈顶元素
Status Pop(LinkStack *S,SElemType *e)
{
LinkStackPtr p;
if (isStackEmpty(*S))
return ERROR;
*e=S->top->data;
p=S->top;
S->top=S->top->next;
S->count--;
free(p); //注意:链表在添加的时候要分配新的存储空间;同理,删除的时候要释放空间
return OK;
}
Status visitLinkStack(SElemType c)
{
printf("%d",c);
return OK;
}
//遍历链栈
Status StackTraverse(LinkStack S)
{
LinkStackPtr p;
p=S.top;
while(p)
{
printf("%d ",p->data);
// visitLinkStack(p->data);
p=p->next;
}
printf("\n");
return OK;
}
int main()
{
int j;
LinkStack s;
int e;
if (InitStack(&s)==OK)
{
for (j=1;j<=10;j++)
{
Push(&s,j);
}
}
printf("栈中元素依次为:");
StackTraverse(s);
}
5、栈的应用
首先,说一下栈的作用:这里,引用大话数据结构这本书中举的例子,非常有道理。
问题:有数组和链表直接实现所要求的功能就好,为什么还要引入栈这种数据结构??
回答:类比到生活中。人有两只脚走路,那么为什么还要乘火车汽车和飞机出行?!为了节省时间和精力。同理,栈的引入是为了简化程序设计中的问题。若仅使用数组等,就要分散精力去考虑数组的下标增减等细节问题,反而掩盖问题的本质。
接下来,讨论栈的应用:
1、应用1——递归。
定义:将一个直接调用自己或通过一系列的调用语句间接地调用自己的函数,成为递归函数。
迭代VS递归:一般结束的条件,迭代使用的是循环结构,递归使用选择结构。
栈和递归:递归的退回的顺序是它前行顺序的逆序。在退回过程中,可能又要执行某些操作,包括恢复在前行过程中存储的某些数据。这种处理思路,非常适合栈这种数据结构。因此用栈去实现递归。
即:①前行阶段,对于每一层递归,函数的局部变量、参数值、以及返回地址等都被压入栈中;②退回阶段,位于栈顶的局部变量、参数值和返回地址等被弹出,用于返回调用层次中执行代码的其余部分,恢复调用状态。
2、应用2——四则运算表达式求值
中缀运算符与前缀、后缀之间的转换。
①中缀转后缀:从前向后遍历,数字入栈后直接出栈,操作符经判断再出栈。操作符判断其优先级,若是最低优先级,则先将栈中所有符号出栈,再让该符号入栈。
②中缀转前缀:从后向前遍历,借用两个栈,一个存数字,一个存符号,符号遇到括号就输出压入数字栈;数字入栈结束,将所有的符号从符号栈中出栈到数字栈。最后数字栈出栈结果即为前缀表达式。
具体可参考博客:
https://blog.csdn.net/Annfan123/article/details/52088320