栈
栈是线性表的一种,只不过它是 操作受限的线性表。
1. 栈的定义和特点
栈是仅在表尾进行插入或删除操作的线性表。 不含元素的空表称为空栈。
对栈来说, 表尾端被称为栈顶; 表头端被称为栈底。
下图为进栈、出栈示意图:
a 1 a_1 a1为栈底元素, a n a_n an为栈顶元素。栈中元素按 a 1 , a 2 , ⋯ , a n a_1, a_2, \cdots , a_n a1,a2,⋯,an的次序进栈, 退栈的第一个元素应为栈顶元素。
栈的修改规则:后进先出(Last In First Out, LIFO)
2. 顺序栈
2.1 存储结构表示
顺序栈是利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。
附设top
指针指示栈顶元素在顺序栈中的位置,以top=0
表示空栈。另设base
指针指示栈底元素在顺序栈中的位置。当top == base
时,表示空栈。
对顺序栈进行操作时,也需要一个“操作台”,使用这个操作台能够访问到顺序栈中的任意一个元素,同时也要保存该顺序栈的大小,方便用户了解顺序栈的信息,确保不会越界操作。
typedef struct
{
SElemType *base; //栈底指针
SElemType *top; //栈顶指针
int stacksize; //栈可用的最大容量
}Sqstack;
其中SElemType
代表顺序栈存放的数据类型。
base
为栈底指针。初始化后,指针base
始终指向栈底的位置。若base
的值为NULL
,则表明栈不存在。
top
为栈顶指针,其初值指向栈底。插入新的栈顶元素时,指针top
增1;删除栈顶元素时,指针top
减1。栈空时,
top == base
,都指向栈底;栈非空时,top
始终指向栈顶元素的上一个位置。
顺序栈指针示意图(栈非空):
2.2 初始化
顺序栈的初始化操作就是为顺序栈分配一个预定义大小的数组空间。
算法步骤:
- 为顺序栈动态分配一个最大容量为
MAXSIZE
的数组空间, 使base
指向这段空间的基地址,即栈底 - 栈顶指针
top
初始化为base
, 表示栈为空 stacksize
置为栈的最大容量MAXSIZE
#define MAXSIZE 100 //数据可能到达的长度
Status InitStack(Sqstack *S)
{
S->base = (SElemType*)malloc(sizeof(SElemType)*MAXSIZE); //为顺序栈分配指定大小的空间
if (S->base == NULL) exit(OVERFLOW); //内存分配失败就退出
S->top = S->base; //栈顶指针也初始化为base,空栈
S->stacksize = MAXSIZE; //stacksize设置为栈的最大容量MAXSIZE
return OK;
}
其中,SElemType
是数据元素的类型,Sqstack
是操作台的类型,Status
是函数的返回值,代表函数的结果状态。
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status;
2.3 入栈
在栈顶插入一个新元素。
算法步骤:
- 判断栈是否满, 若满则返回
ERROR
- 将新元素压入栈顶, 栈顶指针加1
Status Push(Sqstack *S, SElemType e)
{
if (S->top-S->base == S->stacksize) return ERROR; //栈满
*S->top++ = e; //*(S->top++)
return OK;
}
2.4 出栈
将栈顶元素删除。
算法步骤:
- 判断栈是否为空, 若空则返回
ERROR
- 栈顶指针减1,栈顶元素出栈
Status Pop(Sqstack *S, SElemType *e)
{
if (S->top == S->base) return ERROR; //栈空
*e = *--S->top; //*(--S->top)
return OK;
}
2.5 取得栈顶元素
当栈非空时, 返回当前栈顶元素的值, 栈顶指针保待不变。
SElemType GetTop(const Sqstack *S)
{
if (S->top == S->base) return ERROR; //栈空
return *(S->top-1); //返回栈顶元素的值, 栈顶指针不变
}
2.6 案例
给出一个简单的案例,将数据元素类型SElemType
设置为int
,所以这是一个数据元素为int
的顺序栈。
包含上述4种操作,便于理解、应用。
下述代码运行环境:VS2019,其他环境可移植。
#include <stdio.h>
#include <malloc.h>
typedef struct
{
int* base; //栈底指针
int* top; //栈顶指针
int stacksize; //栈可用的最大容量
}Sqstack;
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status;
#define MAXSIZE 100 //数据可能到达的长度
// 初始化顺序栈
// 函数参数: S -> 操作台地址
// 返回值:1代表运行正常
Status InitStack(Sqstack* S)
{
S->base = (int*)malloc(sizeof(int) * MAXSIZE); //为顺序栈分配指定大小的空间
if (S->base == NULL) exit(OVERFLOW); //内存分配失败就退出
S->top = S->base; //栈顶指针也初始化为base,空栈
S->stacksize = MAXSIZE; //stacksize设置为栈的最大容量MAXSIZE
return OK;
}
// 入栈
// 函数参数: S -> 操作台地址; e -> 入栈参数
// 返回值:1代表运行正常; 0代表运行有问题
Status Push(Sqstack* S, int e)
{
if (S->top - S->base == S->stacksize) return ERROR; //栈满
*S->top++ = e; //*(S->top++)
return OK;
}
// 出栈
// 函数参数: S -> 操作台地址; e -> 出栈参数
// 返回值:1代表运行正常; 0代表运行有问题
Status Pop(Sqstack* S, int *e)
{
if (S->top == S->base) return ERROR; //栈空
*e = *--S->top; //*(--S->top)
return OK;
}
// 取得栈顶元素
// 函数参数: S -> 操作台地址
// 返回值:栈顶元素值
int GetTop(const Sqstack* S)
{
if (S->top == S->base) return ERROR; //栈空
return *(S->top - 1); //返回栈顶元素的值, 栈顶指针不变
}
int main()
{
Sqstack stack; //创建顺序栈的操作台
int elem = 0; //存放出栈元素
InitStack(&stack); //初始化栈
Push(&stack, 41); //将数据41入栈
Push(&stack, 56); //将数据56入栈
Push(&stack, 72); //将数据72入栈
printf("%d\n", GetTop(&stack)); //打印栈顶元素
Pop(&stack, &elem); //出栈
printf("elem: %d\n", elem); //打印出栈元素
printf("%d\n", GetTop(&stack)); //打印栈顶元素
Pop(&stack, &elem); //出栈
printf("elem: %d\n", elem); //打印出栈元素
printf("%d\n", GetTop(&stack)); //打印栈顶元素
return 0;
}
3. 链栈
3.1 链栈的结构表示
链栈是指采用链式存储结构实现的栈。通常链栈用单链表来表示。
注:链栈的指针指向是 a n a_n an指向 a n − 1 a_n-1 an−1一直指到 a 1 a_1 a1,这和单链表的指向相反!
- 头指针
S
就是栈顶- 不需要头结点
typedef struct StackNode
{
SElemType data; //数据
struct StackNode * next; //指针
}StackNode, *LinkStack;
由于栈的主要操作是在栈顶插入和删除, 以链表的头部作为栈顶是最方便的, 而且没必要像单链表那样为了操作方便附加一个头结点。
3.2 初始化
链栈的初始化操作就是构造一个空栈, 因为没必要设头结点, 所以直接将栈顶指针置空即可。
Status InitStack(LinkStack *S)
{
*S = NULL; //构造一个空栈, 将栈顶指针置空
return OK;
}
3.3 入栈
链栈在入栈前不需要判断栈是否满,只需要为入栈元素动态分配一个结点空间。
算法步骤:
- 生成新结点
s
,用指针p
指向 - 将新结点
s
的数据域设置为e - 将新结点
s
插入栈顶 - 修改栈顶指针为
p
Status Push(LinkStack *S, SElemType e)
{
StackNode *p = (StackNode*)malloc(sizeof(StackNode)); //1. 生成新结点
p->data = e; //2. 将新结点数据域置为e
p->next = *S; //3. 将新结点插人栈顶
*S = p; //4. 修改栈顶指针为p
return OK;
}
3.4 出栈
链栈在出栈前也需要判断栈是否为空,链栈在出栈后需要释放出栈元素的栈顶空间。
算法步骤:
- 判断栈是否为空,若空则返回ERROR
- 将栈顶元素赋给e
- 临时保存栈顶元素的空间, 以备释放
- 修改栈顶指针, 指向新的栈顶元素
- 释放原栈顶元素的空间
Status Pop(LinkStack *S, SElemType *e)
{
if (*S == NULL) return ERROR; //1. 栈空
*e = (*S)->data; //2. 将栈顶元素赋给e
LinkStack p = *S; //3. 保存栈顶元素的空间, 以备释放
*S = (*S)->next; //4. 修改栈顶指针, 指向新的栈顶元素
free(p); //5. 释放原栈顶元素的空间
return OK;
}
3.5 取得栈顶元素
当栈非空时,返回当前栈顶元素的值,栈顶指针S保持不变。
SElemType GetTop(const LinkStack *S)
{
if (*S == NULL) return ERROR; //栈空
return (*S)->data; //返回栈顶元素的值, 栈顶指针不变
}
3.6 案例
给出一个简单的案例,将数据元素类型SElemType
设置为int
,所以这是一个数据元素为int
的链表。
包含上述4种操作,便于理解、应用。
下述代码运行环境:VS2019,其他环境可移植。
#include <stdio.h>
#include <malloc.h>
typedef struct StackNode
{
int data; //数据
struct StackNode* next; //指针
}StackNode, * LinkStack;
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status;
// 初始化链栈
// 函数参数: S -> 头指针地址
// 返回值:1代表运行正常
Status InitStack(LinkStack* S)
{
*S = NULL; //构造一个空栈, 将栈顶指针置空
return OK;
}
// 入栈
// 函数参数: S -> 头指针地址; e -> 入栈参数
// 返回值:1代表运行正常
Status Push(LinkStack* S, int e)
{
StackNode* p = (StackNode*)malloc(sizeof(StackNode)); //1. 生成新结点
p->data = e; //2. 将新结点数据域置为e
p->next = *S; //3. 将新结点插人栈顶
*S = p; //4. 修改栈顶指针为p
return OK;
}
// 出栈
// 函数参数: S -> 头指针地址; e -> 出栈参数
// 返回值:1代表运行正常; 0代表运行有问题
Status Pop(LinkStack* S, int* e)
{
if (*S == NULL) return ERROR; //1. 栈空
*e = (*S)->data; //2. 将栈顶元素赋给e
LinkStack p = *S; //3. 保存栈顶元素的空间, 以备释放
*S = (*S)->next; //4. 修改栈顶指针, 指向新的栈顶元素
free(p); //5. 释放原栈顶元素的空间
return OK;
}
// 取得栈顶元素
// 函数参数: S -> 头指针地址
// 返回值:栈顶元素值
int GetTop(const LinkStack* S)
{
if (*S == NULL) return ERROR; //栈空
return (*S)->data; //返回栈顶元素的值, 栈顶指针不变
}
int main()
{
LinkStack stack; //创建链栈的头指针
int elem = 0; //存放出栈元素
InitStack(&stack); //初始化栈
Push(&stack, 41); //将数据41入栈
Push(&stack, 56); //将数据56入栈
Push(&stack, 72); //将数据72入栈
printf("%d\n", GetTop(&stack)); //打印栈顶元素
Pop(&stack, &elem); //出栈
printf("elem: %d\n", elem); //打印出栈元素
printf("%d\n", GetTop(&stack)); //打印栈顶元素
Pop(&stack, &elem); //出栈
printf("elem: %d\n", elem); //打印出栈元素
printf("%d\n", GetTop(&stack)); //打印栈顶元素
return 0;
}