【数据结构】栈的基础知识


栈是线性表的一种,只不过它是 操作受限的线性表

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 初始化

顺序栈的初始化操作就是为顺序栈分配一个预定义大小的数组空间。

算法步骤:

  1. 为顺序栈动态分配一个最大容量为MAXSIZE的数组空间, 使base指向这段空间的基地址,即栈底
  2. 栈顶指针top初始化为base, 表示栈为空
  3. 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 入栈

在栈顶插入一个新元素。

算法步骤:

  1. 判断栈是否满, 若满则返回ERROR
  2. 将新元素压入栈顶, 栈顶指针加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 出栈

将栈顶元素删除。

算法步骤:

  1. 判断栈是否为空, 若空则返回ERROR
  2. 栈顶指针减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 an1一直指到 a 1 a_1 a1,这和单链表的指向相反!

  1. 头指针S就是栈顶
  2. 不需要头结点
typedef struct StackNode
{
    SElemType data; 			//数据
    struct StackNode * next;	//指针
}StackNode, *LinkStack;

由于栈的主要操作是在栈顶插入和删除, 以链表的头部作为栈顶是最方便的, 而且没必要像单链表那样为了操作方便附加一个头结点。

3.2 初始化

链栈的初始化操作就是构造一个空栈, 因为没必要设头结点, 所以直接将栈顶指针置空即可。

Status InitStack(LinkStack *S)
{
    *S = NULL; //构造一个空栈, 将栈顶指针置空
    return OK;
}

3.3 入栈

链栈在入栈前不需要判断栈是否满,只需要为入栈元素动态分配一个结点空间。

算法步骤:

  1. 生成新结点s,用指针p指向
  2. 将新结点s的数据域设置为e
  3. 将新结点s插入栈顶
  4. 修改栈顶指针为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 出栈

链栈在出栈前也需要判断栈是否为空,链栈在出栈后需要释放出栈元素的栈顶空间

算法步骤:

  1. 判断栈是否为空,若空则返回ERROR
  2. 将栈顶元素赋给e
  3. 临时保存栈顶元素的空间, 以备释放
  4. 修改栈顶指针, 指向新的栈顶元素
  5. 释放原栈顶元素的空间

链栈出栈

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值