【数据结构篇C++实现】- 栈

文章目录

  • 🚀一、栈的原理精讲
  • 🚀二、栈的算法实现
    • ⛳栈的顺序存储结构
      • 🎉(一)顺序栈
        • 1.栈的结构体定义
        • 2.栈的初始化
        • 3.判断空栈
        • 4.判断栈满
        • 5.元素入栈
        • 6.元素出栈
        • 7.获取栈顶元素
      • 🎉(二)共享栈
        • 1.共享栈的结构体定义
        • 2.共享栈进栈
        • 3.共享栈出栈
    • ⛳栈的链式存储结构
      • 🎉链栈
        • 1.链栈的结构体定义
        • 2.链栈的初始化
        • 3.链栈的入栈
        • 4.链栈的出栈
        • 5.取栈顶元素、遍历链栈


🚀一、栈的原理精讲

拿一个故事引入一下:

我拼搏了几年后,终于有点小钱能买一辆车了,但是,我家住在巷子里,在一个胡同的尽头,而且整个胡同非常窄,只能容纳一辆车通过,而且是死胡同,我每天都要为停车发愁。当我回家早,把车停在胡同里面,早上上班一起来,我勒个去,我的车在胡同最里面,这到胡同口,一辆辆的车哇,我还要一辆一辆打电话,让胡同口那辆车出去,然后挨着一辆一辆出去,才能开到我的车。每天上班我都迟到,所以之后我干脆下班后再加班,晚点回来,等天黑了,别的车都停进去了再回家,再回去把车停在胡同口,这样早上就可以第一个去上班了。

在这里插入图片描述

胡同里的小汽车是排成一条直线,是线性排列,而且只能从一端进出,后进的汽车先出去,后进先出(Last In First Out,简称LIFO),这就是"栈"。栈也是一种线性表,只不过它是操作受限的线性表,只能在一端操作

栈(Stack):是只允许在一端进行插入或删除的线性表。首先栈是一种线性表,但限定这种线性表只能在某一端进行插入和删除操作。进出的一端称为栈顶(top),另一端称为栈底(base)。栈可以用顺序存储,也可以用链式存储。

栈的核心要素:

  • 栈顶(Top):线性表允许进行插入删除的那一端。
  • 栈底(Base):固定的,不允许进行插入和删除的另一端。
     

🚀二、栈的算法实现

⛳栈的顺序存储结构

🎉(一)顺序栈

采用顺序存储的栈称为顺序栈,它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素。其中,base 指向栈底,top 指向栈顶。

(本篇采用的方法,top指向的是下一个要插入的位置,即栈顶元素的下一个位置,而不是栈顶元素,在有些地方可以看见,top指向的就是栈顶的元素)

注意:栈只能在一端操作,后进先出,这是栈的关键特征,也就是说不允许在中间查找、取值、插入、删除等操作,我们掌握好顺序栈的初始化、入栈,出栈,取栈顶元素等操作即可。

在这里插入图片描述

1.栈的结构体定义

#define MaxSize 128 //预先分配空间,这个数值根据实际需要预估确定

typedef int ElemType;

typedef struct _SqStack{
    ElemType *base; //栈底指针
    ElemType *top; //栈顶指针
}SqStack;

解释:

  1. 栈底指针同时也就是我们分配空间的基地址
  2. 也可改为ElemType base[MAXSIZE],但为了理解上方便,最好用指针分配空间
  3. 栈顶指针同样也不用设置为指针,因为是数组,定义为普通整形表示下标位置也可

2.栈的初始化

bool InitStack(SqStack &S) { //构造一个空栈 S
    S.base = new int[MaxSize];//为顺序栈分配一个最大容量为 Maxsize 的空间

    if (!S.base) return false; //空间分配失败

    S.top=S.base; //top 初始为 base,空栈

    return true;
}

//采用top指向栈顶元素的方法
bool StackEmpty(SqStack S){
    S->top = -1;    //初始化栈顶指针
}

在这里插入图片描述

解释:

当采用top指向栈顶元素的方法时,并且以ElemType base[MAXSIZE]方式分配空间,初始化操作直接将top = -1即可

3.判断空栈

bool IsEmpty(SqStack &S){ //判断栈是否为空
    if (S.top == S.base){
    	return true;
    } else {
    	return false;
    }
}

//采用top指向栈顶元素的方法
bool StackEmpty(SqStack S){
    if(S.top == -1){    
        return true;    //栈空
    }else{  
        return false;   //不空
    }
}

解释:

采用top指向栈顶元素的方法时,当栈存在一个元素时,top等于0,因此通常把空栈的判断条件定位top等于-1。

4.判断栈满

//本篇方法
S.top-S.base == MaxSize;
    
//采用top指向栈顶元素的方法
S->top == MAXSIZE-1

在这里插入图片描述

解释:

指针的减法表示中间相隔多少个元素,基础知识不多讲

5.元素入栈

bool PushStack(SqStack &S, int e) { // 插入元素 e 为新的栈顶元素
    if (S.top-S.base == MaxSize) //栈满
    	return false;
    
    *(S.top++) = e; //元素 e 压入栈顶,然后栈顶指针加 1,等价于*S.top=e;S.top++;
    return true;
}

//采用top指向栈顶元素的方法
/*插入元素e为新的栈顶元素*/
Status Push(SqStack *S, ElemType e){
    //满栈
    if(S->top == MAXSIZE-1){
        return ERROR;
    }
    S->top++;   //栈顶指针增加一
    S->data[S->top] = e;    //将新插入元素赋值给栈顶空间
    return OK;
}

在这里插入图片描述

6.元素出栈

bool PopStack(SqStack &S, ElemType &e) //删除 S 的栈顶元素,暂存在变量 e中
{
    if (S.base == S.top){ //栈空
    	return false;
    }
    
    e = *(--S.top); //栈顶指针减 1,将栈顶元素赋给 e
    
    return true;
}

//采用top指向栈顶元素的方法
/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status Pop(SqStack *S, ElemType *e){
    if(S->top == -1){  //栈空
        return ERROR;
    }
    *e = S->data[S->top];   //将要删除的栈顶元素赋值给e
    S->top--;   //栈顶指针减一
    return OK;
}

在这里插入图片描述

解释:

出栈操作: 和入栈相反,出栈前要判断是否栈空,如果栈是空的,则出栈失败,否则将栈顶元素暂存给一个变量,栈顶指针向下移动一个空间(top–)。

7.获取栈顶元素

ElemType GetTop(SqStack &S) { //返回 S 的栈顶元素,栈顶指针不变	
    if (S.top != S.base){ //栈非空
    	return *(S.top - 1); //返回栈顶元素的值,栈顶指针不变
    } else {
    	return -1;
    }
}

//采用top指向栈顶元素的方法
/*读栈顶元素*/
Status GetTop(SqStack S, ElemType *e){
    if(S->top == -1){   //栈空
        return ERROR;
    }
    *e = S->data[S->top];   //记录栈顶元素
    return OK;
}

🎉(二)共享栈

利用栈底位置相对不变的特征,可让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸,如下图所示:(共享栈我们就以第二种方式再来讲解一下)

两个栈的栈顶指针都指向下一个可插入位置,S1.top=S1.base时1号栈为空,S2.top == S2.base == MaxSize时2号栈为空;仅当两个栈顶指针相同(S1.top = S2.top)时,此时能最后插入一个元素。当1号栈进栈时S1.top先赋值再加一,2号栈进栈时S2.top先赋值再减一出栈时则刚好相反。

在这里插入图片描述

两个栈的栈顶指针都指向栈顶元素,S1.top=-1时1号栈为空,S2.top=MaxSize时2号栈为空;仅当两个栈顶指针相邻(S1.top+1=S2.top)时,判断为栈满。当1号栈进栈时S1.top先加1再赋值,2号栈进栈时S2.top先减一再赋值出栈时则刚好相反。

在这里插入图片描述

1.共享栈的结构体定义

/*两栈共享空间结构*/
#define MAXSIZE 50  //定义栈中元素的最大个数
typedef int ElemType;   //ElemType的类型根据实际情况而定,这里假定为int
/*两栈共享空间结构*/
typedef struct{
	ElemType data[MAXSIZE];
	int top0;	//栈0栈顶指针
	int top1;	//栈1栈顶指针
}SqDoubleStack;

解释:

  1. 像之前一样,ElemType data[MAXSIZE];可用指针表示
  2. 栈0栈顶指针和栈1栈顶指针同样也能用指针表示
  3. 栈1的栈底指针就不需要了,其实就是MAXSIZE - 1

2.共享栈进栈

/*插入元素e为新的栈顶元素*/
Status Push(SqDoubleStack *S, Elemtype e, int stackNumber){
    if(S->top0+1 == S->top1){   //栈满
        return ERROR;
    }
    if(stackNumber == 0){   //栈0有元素进栈
        S->data[++S->top0] = e; //若栈0则先top0+1后给数组元素赋值
    }else if(satckNumber == 1){ //栈1有元素进栈
        S->data[--S->top1] = e; //若栈1则先top1-1后给数组元素赋值
    }
    return OK;
}

解释:

对于两栈共享空间的push方法,我们除了要插入元素值参数外,还需要有一个判断是栈0还是栈1的栈号参数stackNumber。

3.共享栈出栈

/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status Pop(SqDoubleStack *S, ElemType *e, int stackNumber){
    if(stackNumber == 0){
        if(S->top0 == -1){
            return ERROR;   //说明栈0已经是空栈,溢出
        }
        *e = S->data[S->top0--]; //将栈0的栈顶元素出栈,随后栈顶指针减1
    }else if(stackNumber == 1){
        if(S->top1 == MAXSIZE){
            return ERROR;   //说明栈1是空栈,溢出
        }
        *e = S->data[S->top1++];    //将栈1的栈顶元素出栈,随后栈顶指针加1
    }
    return OK;
}

⛳栈的链式存储结构

🎉链栈

栈的链式存储结构就是链栈,栈底就是链表的最后一个结点,而栈顶是链表的第一个结点,一个链栈可以由栈顶指针top唯一确定。链栈的元素入栈就类似于链表的头插法

  • 链式存储结构可以更好的避免栈上溢,因为顺序栈在定义结构体时需要定义最大值。
  • 便于多个栈共享存储空间和提高其效率
  • 通常采用单链表实现,并规定所有操作都是在单链表的表头进行的
  • 这里规定链栈没有头节点,Lhead指向栈顶元素,
  • 对于空栈来说,链表原定义是头指针指向空,那么链栈的空其实就是top=NULL的时候。

在这里插入图片描述

1.链栈的结构体定义

//1.定义数据类型
typedef int ElemType;
//2.定义链栈结构体
typedef struct LinkStackNode
{
    ElemType data;//存数据
    struct LinkStackNode *next;//存下个节点的地址
} LinkStack;

解释:

也可以为链栈增加要素,例如增添一个count表示元素数量,但这样就不能跟结点共享一个结构体,需要单独定义:

/*栈的链式存储结构*/
/*构造节点*/
typedef struct LinkStackNode{
    ElemType data;
    struct StackNode *next;
}StackNode, *LinkStackPrt;
/*构造链栈*/
typedef struct LinkStack{
    LinkStackPrt top;
    int count;
}LinkStack;

//这样定义就不需要什么初始化操作了,最多初始化count = 0

2.链栈的初始化

//3.初始化链栈
int initLinkStack(LinkStack *L)
{
    L = (LinkStack *) malloc(sizeof(LinkStack));//申请内存
    if(!L->data) return 0;//申请失败
    L->data = 0;//初始化链栈头结点数据域
    L->next = NULL;//初始化链栈头结点指针域
    return 1;
}

3.链栈的入栈

//4.入栈
int push(LinkStack *L, ElemType e)
{
    LinkStack *n;//新节点
    n = (LinkStack *) malloc(sizeof(LinkStack));
    if(!n->data) return 0;
    
    n->data = e;//存入数据
    n->next = L->next;//链栈栈顶元素链入新节点,新节点变成栈顶
    L->next = n;//新节点链入链栈头结点末尾
    return 1;
}

4.链栈的出栈

//5.出栈
int pop(LinkStack *L, ElemType *e)
{
    if(!L->next) return 0;//栈空,返回0
    
    LinkStack *d = L->next;//出栈指针指向栈顶
    *e = d->data;//赋值
    L->next = d->next;//头结点跳过出栈节点,链入出栈节点的下一节点
    free(d);//释放内存
    return 1;
}

5.取栈顶元素、遍历链栈

//取栈顶
int getTop(LinkStack *L, ElemType *e)
{
    if(!L->next) return 0;
    *e = L->next->data;
    return 1;
}

//遍历链栈
void printStack(LinkStack *L)
{
    LinkStack *p = L;//遍历指针
    while (p)
    {
        p = p->next;
        printf("%d ", p->data);
    }
    printf("\n");
}

其实操作跟链表差距不大,并且比链表的操作还少了很多,都只在链表头部操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/3821.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

冯诺依曼,操作系统以及进程概念

文章目录一.冯诺依曼体系结构二.操作系统(operator system)三.系统调用和库函数四.进程1.进程控制块(PCB)2.查看进程3.系统相关的调用4.fork介绍(并发引入)五.总结一.冯诺依曼体系结构 计算机大体可以说是…

MD5加密竟然不安全,应届生表示无法理解?

前言 近日公司的一个应届生问我,他做的一个毕业设计密码是MD5加密存储的,为什么密码我帮他调试的时候,我能猜出来明文是什么? 第六感,是后端研发的第六感! 正文 示例,有个系统,前…

【深度强化学习】(6) PPO 模型解析,附Pytorch完整代码

大家好,今天和各位分享一下深度强化学习中的近端策略优化算法(proximal policy optimization,PPO),并借助 OpenAI 的 gym 环境完成一个小案例,完整代码可以从我的 GitHub 中获得: https://gith…

泰克信号发生器特点

泰克信号发生器是一种用于产生各种类型的电子信号的仪器,可以广泛应用于电子、通信、自动化、医疗等领域。泰克信号发生器具有以下特点:多种信号类型:泰克信号发生器可以产生多种类型的电子信号,包括正弦波、方波、三角波、脉冲等…

TitanIDE:云原生开发到底强在哪里?

原文作者:行云创新技术总监 邓冰寒 引言 是一种新的软件开发方法,旨在构建更可靠、高效、弹性、安全和可扩展的应用程序。与传统的应用程序开发方式不同,云原生是将开发环境完全搬到云端,构建一站式的云原生开发环境。云原生的开…

PWM互补输出,以及死区时间计算

本文基于野火例程进行解说 实验内容 本次实验输出一对互补的pwm波,且进行死区时间的计算说明。 代码 互补输出对应的定时器初始化代码: bsp_advance_tim.c /********************************************************************************* fi…

【YOLO】YOLOv8训练自定义数据集(4种方式)

YOLOv8 出来一段时间了,继承了分类、检测、分割,本文主要实现自定义的数据集,使用 YOLOV8 进行检测模型的训练和使用 YOLOv8 此次将所有的配置参数全部解耦到配置文件 default.yaml,不再类似于 YOLOv5,一部分在配置文件…

Anaconda 的安装配置及依赖项的内外网配置

在分享anaconda 的安装配置及使用前,我们必须先明白anaconda是什么;Anaconda是一个开源的Python发行版本。两者区别在于前者是一门编程语言,后者相当于编程语言中的工具包。 由于python自身缺少numpy、matplotlib、scipy、scikit-learn等一系…

Java中的深拷贝和浅拷贝

目录 🍎引出拷贝 🍎浅拷贝 🍎深拷贝 🍎总结 引出拷贝 现在有一个学生类和书包类,在学生类中有引用类型的书包变量: class SchoolBag {private String brand; //书包的品牌private int size; //书…

7.网络爬虫—正则表达式详讲

7.网络爬虫—正则表达式详讲与实战Python 正则表达式re.match() 函数re.search方法re.match与re.search的区别re.compile 函数检索和替换检索:替换:findallre.finditerre.split正则表达式模式常见的字符类正则模式正则表达式模式量词正则表达式举例前言&…

2022财报逆转,有赞穿透迷雾实现突破

2022年,商家经营面临困难。但在一些第三方服务商的帮助下,也有商家取得了逆势增长。 2023年3月23日,有赞发布2022年业绩报告,它帮助许多商家稳住了一整年的经营。2022年,有赞门店SaaS业务的GMV达到425亿元&#xff0c…

24万字智慧城市顶层设计及智慧应用解决方案

本资料来源公开网络,仅供个人学习,请勿商用,如有侵权请联系删除。部分资料内容: 4.8 机房消防系统 4.8.1消防系统概况 根据本工程机房消防系统的特殊要求,整个消防系统由火灾报警系统、消防联动系统和气体灭火系统三部…

常见的嵌入式微处理器(Micro Processor Unit,MPU)

嵌入式微处理器是由通用计算机中的CPU演变而来的。它的特征是具有32位以上的处理器,具有较高的性能,当然其价格也相应较高。但与计算机处理器不同的是,在实际嵌入式应用中,只保留和嵌入式应用紧密相关的功能硬件,去除了…

医院陪诊系统源码,可以提供新的就医方式

随着人们生活水平的提高和医疗服务的进步,越来越多的人们开始注重家庭健康和医疗保健。在这个背景下,陪护系统和医院陪诊系统应运而生,成为了现代医疗服务领域中的重要组成部分。 陪护系统是一种为患者提供家庭养护服务的机构,它…

“蓝桥杯”递推和递归(一)——取数位

1. 算法简介 递推和递归虽然叫法不同,但它们的基本思想是一致的,在很多程序中,这两种算法可以通用,不同的是递推法效率更高,递归法更方便阅读。 (1)递推法 递推法是一种重要的数学方法&#…

【PC自动化测试-4】inspect.exe 详解

1,inspect.exe图解" 检查 "窗口有几个主要部分:● 标题栏。 显示" 检查 HWND (窗口句柄) 。● 菜单栏。 提供对 检查功能 的访问权限。● 工具 栏。 提供对 检查功能 的访问权限。● 树视图。 将 UI 元素的层次结构呈现为树视图控件&…

【超好懂的比赛题解】暨南大学2023东软教育杯ACM校赛个人题解

title : 暨南大学2023东软教育杯ACM校赛 题解 tags : ACM,练习记录 date : 2023-3-26 author : Linno 文章目录暨南大学2023东软教育杯ACM校赛 题解A-小王的魔法B-苏神的遗憾C-神父的碟D-基站建设E-小王的数字F-Uziの真身G-电子围棋H-二分大法I-丁真的小马朋友们J-单车运营K-超…

JavaScript实现列表分页(小白版)

组件用惯了,突然叫你用纯cssJavaScript写一个分页,顿时就慌了。久久没有接触js了,不知道咋写了。本文章也是借与参考做的一个demo案例,小白看了都会的那种。咱们就以ul列表为例进行分页: 首先模拟的数据列表是这样的&a…

变量的理论分布模型

二项分布 定义 对立事件的总体分布,称为二项分布。 例如,一个群体只有男和女,现在进行n次随机抽样调查,随机抽样男出现的次数可能是0,1,2,3,4,…,n, 这种类…

网络安全实战从 0 到 1 彻底掌握 XXE

0x01 什么是 XXE个人认为,XXE 可以归结为一句话:构造恶意 DTD介绍 XXE 之前,我先来说一下普通的 XML 注入,这个的利用面比较狭窄,如果有的话应该也是逻辑漏洞。既然能插入 XML 代码,那我们肯定不能善罢甘休…