3.2、链式栈实现
3.2.1、链式栈说明
- 和单链表类似。链式栈即用一组任意的存储单元存储栈的数据元素。每个结点包括存放数据元素的数据域,和指示下一个栈结点的指针域。
- 对于栈底元素而言,由于其没有下一个结点,故栈底结点的指针域始终为 NULL ,其数据域存放进栈的第一个数据元素。
- 对于栈顶指针,由于栈只能通过栈顶进出数据,故只需要设置一个栈顶指针用于指示栈顶结点即可。链式栈的图解如下图所示。
注:可将链式栈结构看成不带头结点的单链表,将栈顶指针看成头指针,将单链表的头插操作看作入栈,头删看作出栈,而不能对尾部结点进行删插操作。这样易于理解。
3.2.2、链式栈的优缺点及适用场合
优点(相对顺序栈):
- 插入删除操作方便,只需修改相邻结点的指向即可、存储空间动态,不存在溢出与浪费
缺点:
- 需要额外空间来存储每个结点的指向关系、存储单元碎片化,空间利用率不高、查询代价较高,必须从头开始查找
适用情况:
- 应用时,优先考虑顺序栈,因为顺序栈不像顺序队列那么浪费空间,同时顺序栈操作简单,故优先使用顺序栈。若用户无法预估所用栈的最大长度,则宜采用链式栈
3.2.3、存储结构定义及头文件声明
(1)存储结构定义
- 根据上述分析,栈结点包括数据域、指针域,以及一个指示栈顶结点的栈指针(这里同时用该变量指示栈是否存在)。故,定义如下。
typedef struct SNode //链栈结点定义
{
ElemType data;
struct SNode *next; //后续结点指针
} SNode;
typedef SNode *LinkStack; //指向栈结点的指针(类似于单链表的头指针),用于指示栈顶位置
(2)头文件声明
- 头文件声明包括函数结果状态代码 OK、ERROR,数据元素类型 ElemType ,栈的基本操作的函数原型声明,以及需要的库。
注:由于C语言不存在引用,需用传址。这里 LinkStack 虽然是指针类型,但是当改变栈的内容,是改变栈顶指针的指向时,需要传递栈顶指针的地址,也就是二级指针。
#ifndef LINKSTACK_H_
#define LINKSTACK_H_
#define OK 1
#define ERROR 0
typedef int Status; //自定义函数类型,其值是函数结果的状态代码
typedef int ElemType; //自定义数据类型
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
typedef struct SNode //链栈结点定义
{
ElemType data;
struct SNode *next; //后续结点指针
} SNode;
typedef SNode *LinkStack; //指向栈结点的指针(类似于单链表的头指针),用于指示栈顶位置,看做全局变量
//链栈基本操作的函数原型
Status InitStack(LinkStack *S);//二级指针,S本身是一个指针,该函数要更改指针的指向,所以要传二级指针
//构造一个空栈 S
Status DestoryStack(LinkStack *S);
//销毁栈 S。结果使 S 指向NULL,所有结点均释放
Status ClearStack(LinkStack *S);
//将栈 S 置空,结果仅存在 S 的头结点,指向栈底元素,其数据域为空,指针域为NULL
bool StackEmpty(LinkStack S);
//判断栈 S 是否为空
int StackLength(LinkStack S);
//返回栈 S 的元素个数,即栈的长度
Status GetTop(LinkStack S, ElemType *e);
//获取 S 的栈顶元素
Status Push(LinkStack *S, ElemType e);
//插入元素 e 为新的栈顶元素
Status Pop(LinkStack *S, ElemType *e);
//删除栈顶元素,并用 e 返回其值
Status StackTraverse(LinkStack S /*,Status (*visit)()*/);
//从栈底到栈顶依次对栈中元素调用函数 visit()。
#endif
3.2.4、主要操作实现与思路
(1)初始化 InitStack()
- 由于链式栈类似于不带头结点的单链表,所以初始化操作仅需初始化栈顶指针为空即可。
- 这里的形参(LinkStack *S)是一个指向栈顶指针类型的指针,其赋空,是将栈顶指针赋空,故是 *S = NULL。
Status InitStack(LinkStack *S)
{
//构造一个空栈
*S = NULL; //即只有栈顶指针,即类似于单链表的头指针
return OK;
}
(2)置空栈 ClearStack()
- 对于链式栈而言,置空栈是回收每一个栈结点。在循环中,由于栈底元素的 next 域始终为 NULL ,故结点的指针域是否为 NULL 可以作为循环的终止条件。
- 由于栈只能通过栈顶来操作栈结点,所以释放栈顶结点前,要先将栈顶指针下移。
Status ClearStack(LinkStack *S)
{
//置空栈 S,即仅剩栈顶指针
SNode *p = *S;
while (p != NULL)
{
*S = p->next; //栈顶指针先下移
free(p); //释放原栈顶
p = *S;
}
return OK;
}
(3)销毁栈 DestoryStack()
- 由于链式栈与不带头结点的单链表类似,所以销毁栈操作与置空栈操作的效果一致。最终栈顶指针均都指向 NULL。
Status DestoryStack(LinkStack *S)
{
//销毁一个栈 S,销毁即释放所有结点
ClearStack(S);
free(*S);
*S = NULL;
return OK;
}
(4)判空 StackEmpty()
- 参照栈的初始化,当栈顶指针指向 NULL 时,栈即为空栈。
bool StackEmpty(LinkStack S)
{
//判断是否为空
return (S == NULL ? true