数据结构与算法:栈
朋友们大家好啊,在链表的讲解过后,我们本节内容来介绍一个特殊的线性表:栈,在讲解后也会以例题来加深对本节内容的理解
栈
- 栈的介绍
- 栈进出栈的变化形式
- 栈的顺序存储结构的有关操作
- 栈的结构定义与初始化
- 压栈操作
- 出栈操作
- 获取栈顶元素和有效元素个数
- 判断是否为空和栈的销毁
- 栈的链式存储结构的有关操作
- 链表的创建
- 链式栈的定义
- 初始化
- 压栈和出栈
- 检查栈是否为空
- 栈的应用--有效的扩号
栈的介绍
在应用软件中,栈的应用非常普遍,比如使用浏览器上网时,会有一个后退键,点击后可以按访问顺序的逆序加载浏览过的网页
很多类似的软件,比如word等文档或编辑软件都有撤销的操作,也是用栈的方式来实现的
栈是一种特殊的线性数据结构,仅支持在一个位置进行添加元素(称为“入栈”或“push”操作)和移除元素(称为“出栈”或“pop”操作)的操作。这个位置就是栈顶(Top)。由于栈是后进先出(LIFO, Last In First Out)的数据结构,最后一个添加到栈中的元素将是第一个被移除。
栈是限定仅在表尾进行插入和删除操作的线性表
栈首先是一个线性表,说明栈元素具有线性关系,在定义中说在线性表的表尾进行插入和删除操作,这里的表尾是指栈顶
它的特殊之处就在于它的删除和插入始终只能在栈顶进行
栈进出栈的变化形式
首先提一个问题,最先进栈的元素,是不是一定最后出栈呢?
答案是不一定的,在不是所有元素都进栈的情况下,先进去的元素也可以出栈,保证是栈顶元素出栈就可以
举例,如果我们有1、2、3三个数字一次进栈,会有哪些出栈次序呢?
- 第一种:1、2、3进,再3、2、1出,出栈次序为321
- 第二种:1进,1出,2进,2出,3进,3出。进一个出一个,出栈次序为123
- 第三种,1进,2进,2出,1出,3进,3出,出栈顺序为213
- 第四种:1进,1出,2进,3进,3出,2出,出栈顺序为132
- 第五种:1进,2进,2出,3进,3出,1出,出栈次序为231
栈的顺序存储结构的有关操作
对于栈来讲,线性表的操作特性它都具备,由于它的特殊性,特别是插入和删除操作,我们改名为push和pop
线性表是用数组来实现的,对于栈这一种只能一头插入的线性表来说,下表为0的一段作为栈底
栈的结构定义与初始化
typedef int STDataType;typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;
对栈进行初始化,构造initial函数
void StackInit(ST* ps)
{assert(ps);ps->a = NULL;ps->top = -1;ps->capacity = 0;}
首先assert断言ps是否为空指针,将a指向NULL,capacity置为0,而top置为-1
在栈的实现中,top变量一般用来指示栈顶元素的位置。对于一个空栈来说,不存在任何元素,因此没有一个合理的位置可以被称为栈顶。在这种情况下,需要一个特殊的值来表示栈是空的
在进行入栈和出栈操作时,top的更新逻辑变得简单直接。例如,每当添加一个新元素到栈中时,先将top加1(这将把top从-1改为0,表示第一个元素的位置),然后在top对应的位置上存放新元素
保证top指向栈顶元素
压栈操作
void StackPush(ST* ps, STDataType x) {assert(ps != NULL); // 检查栈是否已满if (ps->top + 1 == ps->capacity) {int newcapacity=ps->capacity==0?4:ps->capacity*2; STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);if (tmp == NULL) {perror("realloc fail");return;}ps->a = tmp;ps->capacity = newcapacity;}// 先将栈顶索引top增加1,然后在新的栈顶位置存入元素xps->top++;ps->a[ps->top] = x;
}
- 首先检查栈是否已满,即:
if (ps->top + 1 == ps->capacity)
。这是通过比较top + 1(即如果添加新元素后的栈顶索引)和capacity(栈的容量)来实现的。 - 如果栈满,执行扩容操作。新的容量newcapacity为当前容量的两倍,但如果当前容量为0,则初始化容量为4。
- 使用realloc尝试扩容
- 栈顶索引top增加1,以便于在正确的位置添加新元素。
- 在新的栈顶位置存入元素x,即
ps->a[ps->top] = x
;
出栈操作
void StackPop(ST* ps) {assert(ps != NULL); if (ps->top == -1) { printf("栈已空,无法执行出栈操作。\n");return;}ps->top -= 1;
}
两个操作没有涉及任何循环,时间复杂度均为O(1);
获取栈顶元素和有效元素个数
STDataType StackTop(ST* ps) {assert(ps != NULL);if (ps->top == -1) {printf("错误:试图从空栈中获取元素。\n");exit(EXIT_FAILURE); }return ps->a[ps->top];
}
int StackSize(ST* ps) {assert(ps != NULL); return ps->top + 1;
}
判断是否为空和栈的销毁
bool StackEmpty(ST* ps) {assert(ps != NULL); return ps->top == -1;
}
在C语言中,当一个函数的返回类型被声明为bool(需要包含<stdbool.h>头文件),那么它只能返回两个值之一:true或false。
- true通常被定义为整数1。
- false被定义为整数0。
这意味着,当你看到一个函数的返回类型是bool,你可以期望该函数根据其执行的操作或检查的条件,返回表示“真”或者“假”的结果。这样的函数通常用于进行某种条件检测或确认某事是否成立。
这行代码核心地检查栈是否为空。在这里,ps->top是栈顶元素的索引。通常情况下,当栈为空时,栈顶索引top被设置为-1来表示栈内没有元素。如果ps->top等于-1,函数返回true,表示栈为空;否则返回false,表示栈中有元素。
void StackDestroy(ST* ps) {assert(ps != NULL); // 确保栈指针ps非空free(ps->a); // 释放动态数组ps->a = NULL; // 将指针设为NULL,防止悬挂指针ps->top = -1; // 重置栈顶指标ps->capacity = 0; // 重置栈容量
}
栈的链式存储结构的有关操作
讲完了栈的顺序存储,我们接着来看栈的链式存储
思考一下,栈只在栈顶进行删除和插入,那么栈顶是放在链表的头端还是尾端呢?
当使用链表实现链式栈时,通常选择链表的头部作为栈顶,因为这种方法更高效、实现也更简单:
- 在链表头部插入或删除节点只需要O(1)的时间复杂度,因为这些操作不需要遍历整个链表。这对于栈操作(即push和pop操作)非常理想,因为它们也应该是O(1)的时间复杂度
- 链表有头指针,栈有顶部指针,可以做到合二为一
链表的创建
typedef int STDataType;typedef struct StackNode {STDataType data; struct StackNode* next;
} StackNode;
链式栈的定义
typedef struct LinkedStack{StackNode* top; int size;
} LinkedStack;
初始化
初始化一个空栈,只需要将栈顶指针设置为NULL,栈的大小设置为0
void Initialize(LinkedStack* stack) {stack->top = NULL;stack->size = 0;
}
压栈和出栈
void Push(LinkedStack* stack, STDataType x) {StackNode* newNode = (StackNode*)malloc(sizeof(StackNode));if (newNode == NULL) {printf("Memory allocation failed\n");return;}newNode->data = x ;newNode->next = stack->top; // 新节点的下一个节点就是当前的栈顶stack->top = newNode; // 更新栈顶为新节点stack->size++;
}
推入新元素需要创建一个新的节点,并将其插入到链表的头部。
int Pop(LinkedStack* stack) {if (stack->top == NULL) { // 检查栈是否为空printf("Stack is empty\n");return -1; // 使用-1表示错误情况,实际使用中应考虑其他错误处理方式}StackNode* temp = stack->top; // 临时保存栈顶节点int data = temp->data; // 获取栈顶数据stack->top = temp->next; // 更新栈顶指针为下一个节点free(temp); // 释放原栈顶节点的内存stack->size--;return data; // 返回栈顶数据
}
弹出栈顶元素先要检查栈是否为空。如果不为空,将栈顶节点从链表中移除,并释放它所占用的内存。
检查栈是否为空
检查链式栈是否为空也很简单,只需检查栈顶指针是否为NULL。
int IsEmpty(LinkedStack* stack) {return stack->top == NULL;
}
栈的应用–有效的扩号
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
这个问题可以通过使用栈来轻松解决。基本思想是遍历字符串中的每个字符,对于每个开放括号((
, {
, [
),我们将其推入栈中。对于每个关闭括号()
, }
, ]
),我们检查它是否与栈顶的开放括号匹配。如果匹配,则弹出栈顶元素并继续处理字符串的下一个字符。如果在任何时候遇到不匹配的情况,或者在遍历完字符串后栈不为空,则字符串不是有效的
typedef char STDataType;typedef struct Stack
{STDataType* a;int top;int capacity;
}ST; void StackInit(ST* ps)
{assert(ps);ps->a = NULL;ps->top = -1;ps->capacity = 0;}void StackPush(ST* ps, STDataType x) {assert(ps != NULL); if (ps->top + 1 == ps->capacity) {int newcapacity=ps->capacity==0?4:ps->capacity*2;STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);if (tmp == NULL) {perror("realloc fail");return;}ps->a = tmp;ps->capacity = newcapacity;}ps->top += 1;ps->a[ps->top] = x;
}
void StackPop(ST* ps) {assert(ps != NULL); if (ps->top == -1) {printf("栈已空,无法执行出栈操作。\n");return;}ps->top -= 1;
}
STDataType StackTop(ST* ps) {assert(ps != NULL); if (ps->top == -1) {printf("错误:试图从空栈中获取元素。\n");exit(EXIT_FAILURE); }return ps->a[ps->top];
}
int StackSize(ST* ps) {assert(ps != NULL); return ps->top + 1;
}
bool StackEmpty(ST* ps) {assert(ps != NULL); return ps->top == -1;
}
void StackDestroy(ST* ps) {assert(ps != NULL); free(ps->a); ps->a = NULL; ps->top = -1; ps->capacity = 0;
}
我们首先列出准备好的函数,这里的数据类型为字符类型,只需要将typedef int STDataType
;改为typedef char STDataType;
bool isValid(char* s)
{ST sa;StackInit(&sa);while(*s){if(*s=='['||*s=='{'||*s=='('){StackPush(&sa,*s);}else{if(StackEmpty(&sa))return false;char top=StackTop(&sa);StackPop(&sa);if(*s==']'&& top!='['||*s=='}'&&top!='{'||*s==')'&&top!='('){return false;}}++s;}bool ret =StackEmpty(&sa);StackDestroy(&sa);return ret;
}
使用while(*s)循环遍历字符串s中的每个字符。对于每个字符有两种情况:
- 左括号(
[, {, (
):如果字符是左括号之一,使用StackPush(&sa,*s);将其推入栈中。 - 右括号(
], }, )
):如果字符是右括号,首先检查栈是否为空,如果空,则立即返回false,表示没有对应的左括号与当前右括号匹配。如果栈不为空,则获取栈顶元素top=StackTop(&sa);并使用StackPop(&sa);将其从栈中弹出。然后检查栈顶元素是否与当前的右括号匹配,如果不匹配,则返回false。 - 结束条件:遍历结束后,使用
bool ret =StackEmpty(&sa)
;检查栈是否为空。如果栈为空,意味着所有的左括号都已被正确匹配,返回true;否则,返回false。最后,StackDestroy(&sa);销毁栈以释放可能分配的资源
本节内存到此结束!感谢大家的阅读!
相关文章:

数据结构与算法:栈
朋友们大家好啊,在链表的讲解过后,我们本节内容来介绍一个特殊的线性表:栈,在讲解后也会以例题来加深对本节内容的理解 栈 栈的介绍栈进出栈的变化形式 栈的顺序存储结构的有关操作栈的结构定义与初始化压栈操作出栈操作获取栈顶元…...
Newtonsoft.Json设置忽略某些字段
using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace TestProject1 {/// <summary>/// 输出json时,设置忽略哪些…...
【c++每天一题】跳跃游戏
题目 给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。 示例 1࿱…...
【C# 中抓取包含多个屏幕内容的整个桌面】
要在 C# 中抓取包含多个屏幕内容的整个桌面,可以使用 .NET Framework 或者其他第三方库来实现。一种常见的方法是使用 System.Windows.Forms 和 System.Drawing 命名空间中的类来实现屏幕截图。以下是一个示例代码,演示如何抓取包含多个屏幕内容的整个桌…...

数据库管理-第152期 Oracle Vector DB AI-04(20240220)
数据库管理152期 2024-02-20 数据库管理-第152期 Oracle Vector DB & AI-04(20240220)1 常用的向量检索方法聚类图搜索哈希量化 2 Oracle Vector DB中的索引索引(默认) 索引(高级)3 EMBEDDINGSSQL EMBE…...
uniapp app端水印组件封装 一次引入版
直接上代码 <template><view><canvas canvas-id"myCanvas"style"width: 100vw; height: 100vh;opacity: 0;position: fixed;top: -1000px;"></canvas></view> </template><script>export default {name: "…...

最新Unity游戏主程进阶学习大纲(2个月)
过完年了,很多同学开始重新规划自己的职业方向,找更好的机会,准备升职或加薪。今天给那些工作了1~5年的开发者梳理”游戏开发客户端主程”的学习大纲,帮助大家做好面试准备。适合Unity客户端开发者。进阶主程其实就是从固定的几个方面搭建好完整的知识体…...

NoSQL 数据库管理工具,搭载强大支持:Redis、Memcached、SSDB、LevelDB、RocksDB,为您的数据存储提供无与伦比的灵活性与性能!
NoSQL 数据库管理工具,搭载强大支持:Redis、Memcached、SSDB、LevelDB、RocksDB,为您的数据存储提供无与伦比的灵活性与性能! 【官网地址】:http://www.redisant.cn/nosql 介绍 直观的用户界面 从单一应用程序中同…...
基于Spring Boot的多级缓存系统设计
在构建大规模应用时,缓存系统是提高性能的关键因素之一。为了更有效地利用缓存,我们可以设计一个基于Spring Boot的多级缓存系统,结合本地内存缓存(如Caffeine)和分布式缓存(如Redis)。以下是一…...

k8s-配置与存储-配置管理
文章目录 一、配置存储1.1 ConfigMap1.1.1.基于文件夹的创建方式1.1.2指定文件的创建方式1.1.3 配置文件创建configmap 1.2 Secret1.2.1Secret的应用与Docker仓库 Secret设置1. Kubernetes 中的 Secrets:创建 Secret 示例:将 Secret 挂载到 Pod 中的示例…...
c语言实现bellman-ford算法
下面是使用C语言实现Bellman-Ford算法的示例代码。Bellman-Ford算法用于在带权重的图中找到从单个源点到所有其他顶点的最短路径,它也能处理图中包含负权重边的情况。 #include <stdio.h> #include <stdlib.h> #include <limits.h>// 定义边的结构 struct …...
socket与rpc的区别
如今的游戏开发,不搞个跨服玩法都不好意思说在做游戏了(当然,也跟游戏类型有关,一些轻度休闲游戏可以排除在外)。跨服玩法的设计,可以进一步激发玩家追求高战力的虚荣心,也可以汇聚玩家数量&…...

10、内网安全-横向移动域控提权NetLogonADCSPACKDC永恒之蓝
用途:个人学习笔记,有所借鉴,欢迎指正! 背景: 主要针对内网主机中的 域控提权漏洞,包含漏洞探针和漏洞复现利用。 1、横向移动-系统漏洞-CVE-2017-0146(ms17-010,永恒之蓝࿰…...
代码随想录算法训练营第三八天 | 动态规划
目录 动态规划基础斐波那契数爬楼梯使用最小花费爬楼梯 LeetCode 509. 斐波那契数 LeetCode 70. 爬楼梯 LeetCode 746. 使用最小花费爬楼梯 动态规划基础 Dynamic Programming (DP) 如果某一问题有很多重叠子问题,使用动态规划是最有效的。 动态规划中每一个状态…...

【ubuntu2004安装N卡驱动】
软硬件环境 硬件:联想notebook16,显卡4060laptop 软件: ubuntu20.04 驱动安装成功的版本:NVIDIA-Linux-x86_64-535.146.02.run 使用默认的驱动安装,没用原因如下 让手动安装。 手动安装 环境准备: sudo …...

使用 Docker 安装 Kibana 8.4.3
使用 Docker 安装 Kibana 8.4.3 一. 安装启动 Kibana 8.4.3二. 简单使用2.1 向 Elasticsearch 发送请求2.2 搜索2.3 整体页面 前言 这是我在这个网站整理的笔记,有错误的地方请指出,关注我,接下来还会持续更新。 作者:神的孩子都在歌唱 安装k…...

基于python社交网络大数据分析系统的设计与实现
项目:基于python社交网络大数据分析系统的设计与实现 摘 要 社交网络大数据分析系统是一种能自动从网络上收集信息的工具,可根据用户的需求定向采集特定数据信息的工具,本项目通过研究爬取微博网来实现社交网络大数据分析系统功能。对于采集…...

【设计模式】23种设计模式笔记
设计模式分类 模板方法模式 核心就是设计一个部分抽象类。 这个类具有少量具体的方法,和大量抽象的方法,具体的方法是为外界提供服务的点,具体方法中定义了抽象方法的执行序列 装饰器模式 现在有一个对象A,希望A的a方法被修饰 …...
编程笔记 Golang基础 009 标识符和关键字
编程笔记 Golang基础 009 标识符和关键字 一、标识符二、标识符分类(一)空白标识符(又称下划线 _)(二)预声明标识符(三)唯一标识符(四)导出标识符 三、关键字…...
vue3中mockjs模拟获取数据
开发项目的时候,如果后端接口没有出来,前端工程师也不必非得等接口出来才进行下步开发。可以使用mock.js来模拟接口数据,以下就是使用vue3设置hook函数来封装axios请求,配合mock.js来实现的代码,mock的官网 Mock.js 一…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...

android RelativeLayout布局
<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...