前言

本届开始我们将对数据结构中栈的内容进行讲解,那么废话不多说,我们正式进入今天的学习

栈是一种很特殊的线性表,它只能在固定的一端进行插入和删除操作,进行数据的插入和删除的一端叫做栈顶,另外一端叫做栈底,栈中的元素遵守后进先出的原则。

压栈:即数据的插入操作,在栈顶进行

出栈:即数据的删除操作,在栈顶进行

栈在内存中普遍采用顺序的存储结构,但是链式的存储结构也同样可行。因为在链式结构中寻找尾节点的时间较长,所以一般采取反向+头插入的操作来完成链式的存储,或者采用双向链表,但是不推荐,因为多一个向前的指针,会更加麻烦。本节仅对顺序存储结构进行讲解。两种存储方式各有优缺点(链式:没有扩容的消耗,更加节省空间        顺序:CPU高速缓存,效率更高)

「顺序表数据存储在连续的物理空间中,当CPU访问数据时,整个数据块可一次性加载到高速缓存中」

栈的基本存储结构

栈的基本存储结构如下所示(动态):

其中的top(栈顶)也可以称作size,a是栈的指针,capacity是栈的容量

栈的初始化和销毁

通过在C语言时期我们对指针的学习,我们可以知道:在初始化的函数之中,如果仅仅采取传值操作,形式参数的改变不会影响到实际参数,所以我们在初始化和销毁栈的过程之中要采取传地址的操作

在初始化栈的开头,我们需要加入assert进行断言,若为空则不采取操作

在数组开空间这一个步骤上,我们可以采取两种措施:第一种是开几个小的空间,后续再扩容;第二种是在初始化阶段先不进行空间的开辟,后续在处理的时候在开辟空间

(这里的代码采取的是不开辟空间,采用两种方法都是可以的)

栈的销毁代码非常简单,这里就不做过多的解释:

  

栈的入栈和出栈

栈的数据插入不像顺序表和链表一样,有头插入和尾插入。因为栈只能够在一端进行插入和删除,所以栈的操作只有压栈操作

对于入栈的函数,我们需要用到的参数有:一个结构体指针pst、待插入的数据x

在开始书写栈的入栈代码之前,我们需要先明确一下top初始化时的指向。top的指向有两种:一种是指向栈顶元素,另外一种是指向栈顶数据下一个数据。(模拟实现的演示代码采取的是指向下一位)

当top指向当前元素的时候,如果栈中没有任何元素的时候,top的值应该要赋值为-1

这两种指向的方式都是可以的,采取不同的指向方式时初始化top对应的赋值情况也不同:

假设我们采用的是第二种方法,那么我们在进行入栈时的操作就是:

pst->a[pst->top] = x;
pst->top++;

而如果我们采取的是第一种方法,我们就需要让top先++再放入数据x 

接下来就是判断空间是否满了,满了的话就执行扩容操作。同样的,top在初始化时的指向不同时,判满的条件也不同。演示代码采取的是指向下一个元素的指向方法,所以判满的条件就是top等于capacity时:

void STPush(ST* pst, STDataType x)
{assert(pst);//扩容if(pst->top == pst->capacity){int newcapacity = 2 * pst->capacity;STDataType* tmp = realloc(pst->a, newcapacity * sizeof(STDataType));if(tmp == NULL){perror("realloc fail");}pst->a = tmp;pst->capacity = newcapacity;}pst->a[pst->top++] = x;
}

这里我故意提供了一个错误的代码,看看能不能发现问题

这里的代码存在的问题是:我们在初始化栈的时候采取了两种方式,第一种就是先给栈开辟一点小空间,此时采用这样的代码是不存在问题的,因为此时的capacity有数值;但是当我们采取第二种方法时,即不给栈开辟空间时,此时的capacity大小就是0,此时我们在newcapacity中的*2操作就会无效,所以我们在对new capacity进行操作的时候先需要判断一下capacity当前的大小:

int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;

那么假设栈一开始为空,此时的a也指向的是NULL,这样使用realloc会不会出现问题呢?答案是不会的,我们来查看一下realloc的使用说明:

从文档中我们可以清晰的看出,当指向的指针是空的时候,realloc函数相当于malloc函数

那么到此为止,入栈的代码就已经全部实现完成了,接下来我们来完成一下出栈的代码:

void STPop(ST* pst)
{assert(pst);pst->top--;
}

 到这里我们可能会有疑问,为什么STPop这短短的几行代码还需要封装成一个函数,为什么不能在使用的时候直接将代码写出来呢?就如同下面的代码一样:

    ST st;STInit(&st);STPush(&st, 1);STPush(&st, 2);STPush(&st, 3);STPush(&st, 4);STPop(&st);STPop(&st);STPop(&st);STPop(&st);//st.top--;

采取这样的方法其实也是可以的,但是这样会影响程序的可读性,所以这里不建议采取这样的形式.包括访问栈顶元素STTop中也建议采取封装的形式,不要直接访问:

STDataType STTop(ST* pst)
{assert(pst);assert(pst->top > 0);return pst->a[pst->top - 1];
}

我们自己直接去访问栈顶元素可能会出现错误,我们可能会忘记一开始设置top指针设置初始化的指向,导致不知道到底top是栈顶元素还是top-1是栈顶元素,如果直接将它封装成一个函数,就不会存在这样的问题

栈的其他接口实现

接下来我们来实现一下栈的判空操作,由于实现的逻辑非常简单,这里就不做过多地讲解了:

bool STEmpty(ST* pst)
{assert(pst);if(pst->top == 0) return true;return false;
}

接下来是返回栈的元素个数的接口:

int STSize(ST* pst)
{assert(pst);return pst->top;
}

因为栈的逻辑结构非常特殊,是采取的先进先出的结构,我们就不能采用类似于顺序表和链表那样的访问方式来访问栈.要想访问栈中的所有元素,我们就需要先用STTop来访问栈顶元素,然后执行STPop来删除栈顶元素,接着继续访问栈顶元素,一直循环到栈的元素为空为止

    while(!STEmpty(&st)){printf("%d ", STTop(&st));STPop(&st);}

但是采取这样的访问方式仍然存在一个问题:当我们访问完一次栈以后,栈里面所有的元素都被清空了,不便于我们后续的操作

其实这样的操作是合理的,我们期望的就是访问完栈中的某一个元素以后,后续就不能访问到这个元素了,这一点我们在后续的习题中会体现出来

练习题:有效的括号

我们来看一下这个练习题的题目:

刚看到这个题目的时候,我们最直接的思路就是数不同括号的数量,但其实这么做是没有用的,我们来举一个反例:"[([)]]",这组反例的数量是匹配的,但是它并不有效.所以这样的思路显然是不行的,在满足数量匹配的同时我们也要满足顺序的匹配

我们先来考虑一下什么时候需要考虑到括号匹配的问题:当我们取到右括号的时候我们才会需要考虑到括号匹配的问题,我们取到左括号的时候只需让他存入即可

结合这样的特征我们就可以考虑用栈来解决这个问题,当读取到左括号的时候只需要将其存入栈中即可.当读取到右括号的时候,因为最近的左括号在栈顶,我们就要它和最近的左括号匹配一下,如果匹配成功就让左括号也出栈

在写代码的时候我们需要注意,我们在确定是否匹配的时候判断的条件是不匹配就跳出,而不是判断匹配,因为就算是匹配了也需要进入下一次的检查:

            if((top == '(' && *s != ')') || (top == '[' && *s != ']') || (top == '{' && *s != '}'))return false;

到这里根据思路我们就可以初步完成代码:

bool isValid(char* s)
{ST st;STInit(&st);while(*s){if(*s == '(' || *s == '[' || *s == '{') STPush(&st, *s);else{char top = STTop(&st);STPop(&st);if((top == '(' && *s != ')') || (top == '[' && *s != ']') || (top == '{' && *s != '}'))return false;}++s;}STDestory(&st);return true;
}

当我们在提交代码的时候可以发现在某些情况下代码还是存在一定的问题:

 在这样的情况之下,确实是不存在不匹配的问题,但是栈里面还有数据没有处理完,此时就意味着栈中还有左括号没有任何右括号供它匹配,说明左右括号的数量是不相等的,所以我们此时还需要加上判空的操作:

    //栈不为空bool ret = STEmpty(&st);STDestory(&st);return ret;

我们再运行一下代码,发现还是存在问题,这里出现了一个断言错误:

这里的意思是栈为空了以后我们还在调用栈,我们结合这里的测试用例来看一下:

这里给的用例是单独的一个右括号,所以会直接在栈中向前查找,寻找最近的左括号与之匹配,但由于栈中只有一个元素,所以此时再去向前查找就会导致越界,所以这里发生了断言错误.所以我们在判断右括号的时候还要加入一个判空的条件:

bool isValid(char* s)
{ST st;STInit(&st);while(*s){if(*s == '(' || *s == '[' || *s == '{') STPush(&st, *s);else{if(STEmpty(&st)) return false;char top = STTop(&st);STPop(&st);if((top == '(' && *s != ')') || (top == '[' && *s != ']') || (top == '{' && *s != '}'))return false;}++s;}//栈不为空bool ret = STEmpty(&st);STDestory(&st);return ret;
}

修改完以后程序就没有问题了,但是如果我们仔细观察还是可以发现一些小的问题:可能会存在内存泄漏

假设栈中左括号的数量多于右括号的数量,在程序结束的时候就走不到后面的STDestory就已经直接返回了,此时就会存在内存泄漏.虽然这个并不会有太大的影响,但是我们还是要养成良好的习惯.

所以在这里,只要提前就false返回了,我们就对其执行STDestory:

            if(STEmpty(&st)){STDestory(&st);return false;}
            if((top == '(' && *s != ')') || (top == '[' && *s != ']') || (top == '{' && *s != '}')){STDestory(&st);return false;}

结尾

那么到此为止有关栈的内容就已经结束了,希望该文章可以给你带来帮助,下一节将对队列的内容进行梳理,谢谢你的浏览!!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/93089.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/93089.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/93089.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

字符串是数据结构还是数据类型?

比较纠结的一个问题,以下是在网上查到后总结的,不知道对不对,欢迎讨论。这是个触及计算机科学核心概念的精妙问题!字符串既可以被视为一种数据类型,也可以被视为一种数据结构,这取决于你观察的视角和讨论的…

Cline与Cursor深度实战指南:AI编程助手的革命性应用

引言 在AI编程工具快速发展的今天,Cline和Cursor作为两款备受瞩目的AI编程助手,正在重新定义开发者的工作方式。作为一名深度使用这两款工具的开发者,我在过去一年的实践中积累了丰富的经验和独到的见解。本文将从技术角度深入分析Cline和Cur…

根本是什么

根本是什么 根本没有了,枝叶还在么? 没有了内涵,外延还有么? 丢弃了根本,再嗨也是无意义,无根据空虚之乐罢了。 人之所行所言所思所想所念皆欲念、历程感怀,情思。所谓得失过往,时空…

springboot基于Java的人力资源管理系统设计与实现

管理员:登录,个人中心,部门管理,员工管理,培训信息管理,员工奖励管理,员工惩罚管理员工考核管理,调薪信息管理,员工调动管理,员工工资管理员工:注…

金字塔降低采样

文章目录image_scale.hppimage_scale.cppmainimage_scale.hpp #ifndef IMAGE_SCALE_HPP #define IMAGE_SCALE_HPP#include <vector> #include <cstdint> #include <utility> // for std::pair #include <algorithm> #include <string> enum cl…

Filament引擎(四)——光照渲染Froxelizer实现分析

Froxelizer主要是用于filament光照效果的实现&#xff0c;生成光照渲染时所需的必要信息&#xff0c;帮助渲染过程中明确哪些区域受哪些光源所影响&#xff0c;是Filament中保证光照效果渲染效率的核心所在。这部分的源码&#xff0c;可以结合filament官方文档中Light Path部分…

2025 环法对决,VELO Angel Glide 坐垫轻装上阵

2025环法第16赛段的风秃山之巅&#xff0c;当最后一缕夕阳沉入云层&#xff0c;山风裹挟着砾石的气息掠过赛道&#xff0c;一场足以载入史册的激战正酣。帕雷-潘特的肌肉在汗水里贲张&#xff0c;链条与齿轮的咬合声混着粗重喘息&#xff0c;在171.5公里赛程的最后3公里陡坡上&…

Linux程序->进度条

进度条最终效果&#xff1a; 目录 进度条最终效果&#xff1a; 一&#xff1a;两个须知 1&#xff1a;缓冲区 ①&#xff1a;C语言自带缓冲区 ②&#xff1a;缓冲区的刷新策略 2&#xff1a;回车和换行的区别 二&#xff1a;倒计时程序 三&#xff1a;入门板进度条的实…

Python爬虫实战:研究tldextract库相关技术构建新闻网站域名分析爬虫系统

1. 引言 网络爬虫作为一种自动获取互联网信息的技术,在数据挖掘、信息检索、舆情分析等领域有着广泛的应用。Python 因其丰富的库和简洁的语法,成为了开发爬虫的首选语言。tldextract 是 Python 中一个强大的域名解析库,能够准确地从 URL 中提取顶级域名、二级域名等关键信…

【算法-华为机试-火星基地改造】

基地改造题目描述目标输入输出代码实现题目描述 在2XXX年&#xff0c;人们发现了一块火星地区&#xff0c;这里看起来很适合建设新家园。但问题是&#xff0c;我们不能一次性将这片地区的空气变得适合人类居住&#xff0c;得分步骤来。 把这片火星地区想象成一个巨大的棋盘。棋…

C++入门自学Day1-- C语言的宏函数和C++内联函数

一、函数调用开销函数调用会涉及&#xff1a;参数压栈&#xff08;或寄存器传参&#xff09;跳转到函数体返回值处理栈帧销毁这个过程对小函数来说可能非常浪费&#xff0c;因此&#xff0c;宏函数和内联函数的目的就是避免“函数调用的开销”&#xff0c;通过代码展开&#xf…

Pytorch混合精度训练最佳实践

混合精度训练&#xff08;Mixed Precision Training&#xff09;是一种通过结合单精度&#xff08;FP32&#xff09;和半精度&#xff08;FP16/FP8&#xff09;计算来加速训练、减少显存占用的技术。它在保持模型精度的同时&#xff0c;通常能带来 2-3 倍的训练速度提升&#x…

Qt C++动态库SDK在Visual Studio 2022使用(C++/C#版本)

01 将C SDK 集成到 IDE 中以下是在 Microsoft Visual Studio 平台下 SDK 的集成。2.1 Visual Studio 平台下 C/C环境配置及集成到 IDE 中xxx.lib 和 xxx.dll 适合在 Windows 操作系统平台使用&#xff0c;这里以 VS2022 环境为例。2.1.1 C/C 工程环境配置与集成1、C# SDK 接口…

大语言模型 LLM 通过 Excel 知识库 增强日志分析,根因分析能力的技术方案(2):LangChain + LlamaIndex 实现

文章大纲 1 技术原理总览 2 详细实现步骤(含代码) 2.1 环境准备 2.2 Excel → LlamaIndex 节点 2.3 构建向量索引(FAISS 本地) 2.4 Google Cloud 向量检索(可选替换 FAISS) 2.5 LangChain 问答链 A. RAG 模式(向量检索 + LLM 生成) B. SQL 模式(无 RAG,直接查表) 2.…

提升ARM Cortex-M系统性能的关键技术:TCM技术解析与实战指南

文章目录引言一、TCM基础架构与工作原理1.1 TCM的物理特性1.2 与缓存机制的对比1.3 ARM Cortex-M系列对TCM的支持二、TCM的典型应用场景2.1 实时中断处理2.2 低功耗模式下的待机代码2.3 高性能算法执行2.4 系统初始化阶段的关键代码三、实战指南&#xff1a;在STM32H7上配置和优…

大数据之路:阿里巴巴大数据实践——大数据领域建模综述

为什么需要数据建模 核心痛点 数据冗余&#xff1a;不同业务重复存储相同数据&#xff08;如用户基础信息&#xff09;&#xff0c;导致存储成本激增。计算资源浪费&#xff1a;未经聚合的明细数据直接参与计算&#xff08;如全表扫描&#xff09;&#xff0c;消耗大量CPU/内存…

实战演练1:实战演练之命名实体识别

实战演练1:实战演练之命名实体识别 命名实体识别简介 代码 命名实体识别简介 什么是命名实体识别任务 命名实体识别(Named Entity Recognition,简称NER)是指识别文本中具有特定意义的实体,主要包括人名、地名、机构名、专有名词等。通常包括两部分: (1)实体边界识别。(2)确定…

数据结构基础内容(第七篇:堆、哈夫曼树)

# 堆 Heap 优先队列(Priority Queue) 结构性:用 *数组* 表示的完全二叉树; 有序性:任一结点的关键字是其子树所有结点的最大值(或最小值) * “最大堆(MaxHeap)”,也称“大顶堆”:最大值 * “最小堆(MinHeap)”,也称“小顶堆” :最小值 主要操作有: • MaxHeap Create( i…

CS231n-2017 Lecture7训练神经网络(二)笔记

本节主要是神经网络的动态部分&#xff0c;也就是神经网络学习参数和搜索最优超参数的过程梯度检查&#xff1a;进行梯度检查&#xff0c;就是简单地把解析梯度与数值计算梯度进行比较&#xff0c;防止反向传播的逻辑出错&#xff0c;仅在调试过程中使用。有如下技巧 &#xff…

IntelliJ IDEA 中左上方未显示项目根目录问题

问题&#xff1a; 在IDEA中编写代码时&#xff0c;发现左上方只显示项目的子模块&#xff0c;未显示根项目名称。 如图所示&#xff0c;未显示子模块的根项目&#xff1a;问题分析 顶层根目录未被识别为项目根目录&#xff0c;需要手动添加识别。 问题解决 进入File – Project…