【数据结构】栈的应用

目录

0  引言

1  栈在括号匹配中的应用

2  栈在表达式求值中的应用

        2.1 算数表达式

        2.2 中缀表达式转后缀表达式

2.3 后缀表达式求值

3  栈在递归中的应用

3.1 栈在函数调用中的作用

3.2 栈在函数调用中的工作原理

4  总结


0  引言

        栈(Stack)是一种非常基本且重要的数据结构,它们在许多计算机科学和软件工程的应用中都有广泛的用途。

        栈:

                ①括号匹配;

                ②表达式求值;

                ③递归函数调用。

1  栈在括号匹配中的应用

        表达式中有两种括号:圆括号 ( ) 和 方括号 [ ],嵌套的顺序任意,但应为正确的格式。

        例如:( ( [ ] [ ] ) ) 为正确格式。

        但如何用算法实现括号匹配问题?

        思路如下:

        (1)初始一个空栈;

        (2)顺序读入括号;

        (3)当读入的为左括号,将继续读入括号,直到读入第一个右括号。那将检测与之最近的左括号是否与之相匹配,若匹配,则出栈;若不匹配,则退出程序。当程序结束时,栈为空。反之,则表明括号序列的格式不正确。

        代码如下:

#include <stdio.h>  
#include <stdlib.h>  
#include <stdbool.h>  #define MAX_SIZE 100 // 假设栈的最大大小  typedef struct {  char data[MAX_SIZE];  int top;  
} Stack;  // 初始化栈  
void initStack(Stack *s) {  s->top = -1;  
}  // 判断栈是否为空  
bool isEmpty(Stack *s) {  return s->top == -1;  
}  // 入栈  
void push(Stack *s, char c) {  if (s->top >= MAX_SIZE - 1) {  printf("Stack overflow\n");  return;  }  s->data[++s->top] = c;  
}  // 出栈  
char pop(Stack *s) {  if (isEmpty(s)) {  printf("Stack underflow\n");  return '#'; // 返回一个无效字符,或可以选择抛出一个错误  }  return s->data[s->top--];  
}  // 检查两个括号是否匹配  
bool isMatch(char c1, char c2) {  if (c1 == '(' && c2 == ')') return true;  if (c1 == '[' && c2 == ']') return true;  if (c1 == '{' && c2 == '}') return true;  return false;  
}  // 括号匹配函数  
bool isBalanced(char *str) {  Stack s;  initStack(&s);  for (int i = 0; str[i] != '\0'; i++) {  if (str[i] == '(' || str[i] == '[' || str[i] == '{') {  push(&s, str[i]);  } else if (str[i] == ')' || str[i] == ']' || str[i] == '}') {  if (isEmpty(&s)) {  // 栈为空,但遇到了右括号,不匹配  return false;  }  char topChar = pop(&s);  if (!isMatch(topChar, str[i])) {  // 栈顶元素与当前右括号不匹配  return false;  }  }  }  // 如果栈为空,则所有括号都匹配  return isEmpty(&s);  
}  int main() {  char str[MAX_SIZE];  printf("Enter a string with brackets: ");  scanf("%s", str); if (isBalanced(str)) {  printf("The brackets are balanced.\n");  } else {  printf("The brackets are not balanced.\n");  }  return 0;  
}

2  栈在表达式求值中的应用

        2.1 算数表达式

        中缀表达式是人们常用的算术表达式,即操作符以中缀形式处于操作数之间。但在计算机中,中缀表达式相较于前缀和后缀表达式来说,更不易被计算机识别。前缀表达式成为波兰式,后缀表达式又称逆波兰式。

        2.2 中缀表达式转后缀表达式

        (1)手算方法:

        ①根据运算顺序对表达式运算符排号;

        ②根据运算符排号顺序,将运算符及两端的操作数以(左操作数 右操作数 运算符)的顺序重新组合。

        例如:( A + B ) * C + ( D - E ) / F 转后缀表达式的过程如下:

        (2)算法实现:

        ①初始一个栈;

        ②遇到操作数,直接加入后缀表达式;

        ③遇到界限符,若为左括号直接入栈,若为右括号,则依次弹出栈中的运算符,加入后缀表达式,知道弹出左括号为止。需要注意的是,左括号和右括号直接删除,不加入后缀表达式。

        ④遇到运算符,则看运算符的优先级,若高于除左括号外的栈顶元素,则直接入栈。反之,则依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,直到遇到低于他的优先级的运算符,才入栈。

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <stdbool.h>  #define MAX_SIZE 100  typedef struct {  char data[MAX_SIZE];  int top;  
} Stack;  // 初始化栈  
void initStack(Stack *s) {  s->top = -1;  
}  // 判断栈是否为空  
bool isEmpty(Stack *s) {  return s->top == -1;  
}  // 入栈  
bool push(Stack *s, char c) {  if (s->top >= MAX_SIZE - 1) {  return false; // 栈溢出  }  s->data[++s->top] = c;  return true;  
}  // 出栈  
char pop(Stack *s) {  if (isEmpty(s)) {  return '\0'; // 栈空,返回空字符  }  return s->data[s->top--];  
}  // 获取栈顶元素,但不弹出  
char peek(Stack *s) {  if (isEmpty(s)) {  return '\0'; // 栈空,返回空字符  }  return s->data[s->top];  
}  // 运算符的优先级比较(这里只处理了基本的四则运算)  
int precedence(char op) {  if (op == '+' || op == '-') {  return 1;  }  if (op == '*' || op == '/') {  return 2;  }  return 0; // 如果不是运算符,返回0  
}  // 将中缀表达式转换为后缀表达式  
void infixToPostfix(char *infix, char *postfix) {  Stack s;  initStack(&s);  int i = 0, j = 0;  while (infix[i] != '\0') {  if (infix[i] >= '0' && infix[i] <= '9') {  // 如果是操作数,直接添加到后缀表达式中  postfix[j++] = infix[i++];  postfix[j++] = ' '; // 假设操作数都是个位数,用空格分隔  } else if (infix[i] == '(') {  // 如果是左括号,直接入栈  push(&s, infix[i++]);  } else if (infix[i] == ')') {  // 如果是右括号,则弹出栈中元素直到遇到左括号  while (!isEmpty(&s) && peek(&s) != '(') {  postfix[j++] = pop(&s);  postfix[j++] = ' ';  }  // 弹出左括号,但不加入后缀表达式  pop(&s);  i++;  } else {  // 如果是运算符  while (!isEmpty(&s) && precedence(peek(&s)) >= precedence(infix[i])) {  // 如果栈不为空且栈顶元素优先级高于或等于当前运算符,弹出栈顶元素  postfix[j++] = pop(&s);  postfix[j++] = ' ';  }  // 当前运算符入栈  push(&s, infix[i++]);  }  }  // 弹出栈中剩余的所有运算符  while (!isEmpty(&s)) {  postfix[j++] = pop(&s);  postfix[j++] = ' ';  }  // 添加字符串结束符  postfix[j] = '\0';  
}  int main() {  char infix[MAX_SIZE], postfix[MAX_SIZE * 2]; // 后缀表达式可能更长,因此分配更多空间  printf("Enter an infix expression: ");  scanf("%s", infix); // 注意:这里不会处理空格和复杂输入  infixToPostfix(infix, postfix);  printf("Postfix expression: %s\n", postfix);  return 0;  
}

2.3 后缀表达式求值

        后缀表达式(也称为逆波兰表示法或逆波兰记法)是一种不需要括号来标明运算符的优先级的数学表达式。在这种表示法中,所有的运算符都放在操作数的后面。

        求值后缀表达式的基本步骤如下:

  • 初始化一个栈,用于存储操作数。
  • 从左到右扫描后缀表达式。
  • 如果扫描到操作数,则将其压入栈中。
  • 如果扫描到运算符,则从栈中弹出两个操作数(先弹出的为右操作数,后弹出的为左操作数),将这两个操作数作为运算符的输入进行运算,然后将结果压回栈中。
  • 重复步骤2-4,直到后缀表达式扫描完毕。
  • 栈中剩下的元素就是表达式的值。

        示例

        后缀表达式:3 4 + 5 *

        求值过程:

  1. 扫描到 3,压入栈:[3]
  2. 扫描到 4,压入栈:[3, 4]
  3. 扫描到 +,弹出 4 和 3,计算 3 + 4 得到 7,压入栈:[7]
  4. 扫描到 5,压入栈:[7, 5]
  5. 扫描到 *,弹出 5 和 7,计算 7 * 5 得到 35,压入栈:[35]
  6. 扫描完毕,栈中元素 35 即为表达式的值。

        下面是实现代码(以上述示例为例):

#include <stdio.h>  
#include <stdlib.h>  
#include <ctype.h>  
#include <string.h>  #define MAX_STACK_SIZE 100  typedef struct {  double data[MAX_STACK_SIZE];  int top;  
} Stack;  // 初始化栈  
void initStack(Stack *s) {  s->top = -1;  
}  // 判断栈是否为空  
int isEmpty(Stack *s) {  return s->top == -1;  
}  // 压栈操作  
void push(Stack *s, double value) {  if (s->top >= MAX_STACK_SIZE - 1) {  printf("Stack overflow\n");  exit(1);  }  s->data[++s->top] = value;  
}  // 弹栈操作  
double pop(Stack *s) {  if (isEmpty(s)) {  printf("Stack underflow\n");  exit(1);  }  return s->data[s->top--];  
}  // 求值后缀表达式  
double evaluatePostfix(const char *postfix) {  Stack s;  initStack(&s);  const char *token = strtok((char *)postfix, " "); // 假设操作符和操作数之间用空格分隔  while (token != NULL) {  if (isdigit(token[0])) { // 如果是操作数  double value = atof(token);  push(&s, value);  } else { // 如果是运算符  double rightOperand = pop(&s); // 弹出右操作数  double leftOperand = pop(&s); // 弹出左操作数  switch (token[0]) {  case '+':  push(&s, leftOperand + rightOperand);  break;  case '-':  push(&s, leftOperand - rightOperand);  break;  case '*':  push(&s, leftOperand * rightOperand);  break;  case '/':  if (rightOperand != 0.0) {  push(&s, leftOperand / rightOperand);  } else {  printf("Error: Division by zero\n");  exit(1);  }  break;  default:  printf("Error: Unknown operator\n");  exit(1);  }  }  token = strtok(NULL, " "); // 继续获取下一个token  }  if (!isEmpty(&s)) {  return pop(&s); // 栈中剩下的元素就是表达式的值  } else {  printf("Error: Invalid postfix expression\n");  exit(1);  }  
}  int main() {  const char *postfix = "3 4 + 5 *";  double result = evaluatePostfix(postfix);  printf("Result: %lf\n", result);  return 0;  
}

3  栈在递归中的应用

3.1 栈在函数调用中的作用

  • 参数传递:当调用一个函数时,需要传递参数给该函数。这些参数会被压入栈中,以便函数内部能够访问和使用它们。
  • 局部变量分配:函数内部定义的局部变量会在栈上分配空间。这些变量的生命周期与函数的执行周期相同,当函数执行完毕后,这些局部变量所占用的栈空间会被自动释放。
  • 保存调用的返回地址:在函数调用时,CPU需要知道函数执行完毕后应该返回到哪个位置继续执行。这个返回地址会被保存在栈中,以便函数执行完毕后能够正确地返回到调用它的位置。
  • 保存寄存器以供恢复:在函数调用和返回的过程中,CPU的寄存器状态会发生变化。为了能够在函数返回后恢复原来的寄存器状态,栈会保存这些寄存器的值。

3.2 栈在函数调用中的工作原理

  • 函数调用:当调用一个函数时,系统首先会创建一个新的栈帧(stack frame)来保存该函数的执行环境。这个栈帧包含了函数的返回地址、参数、局部变量等信息。然后,系统会将当前程序的执行状态(如返回地址、寄存器状态等)压入栈中,以便在函数执行完毕后能够恢复。
  • 函数执行:在函数执行过程中,函数会访问栈帧中的参数和局部变量,并根据需要进行计算和操作。同时,如果函数内部调用了其他函数,系统也会为这些被调用的函数创建新的栈帧,并将当前函数的执行状态压入栈中保存。
  • 函数返回:当函数执行完毕或者遇到return语句时,系统会弹出当前函数的栈帧,并根据栈帧中的返回地址返回到调用它的位置继续执行。在返回之前,系统还会恢复调用该函数时的寄存器状态。

        下面将给出一个例子:

        例如:阶乘,大家可以自行调试;

#include <stdio.h>int step(int n){if(n==1)return 1;elsereturn n*step(n-1);
}int main(){int n,s;scanf("%d",&n);s=step(n);printf("%d",s);
}

4  总结

        在本文中,我们深入探讨了栈这一数据结构及其在各种应用场景中的重要作用。栈作为一种后进先出(LIFO)的数据结构,其独特的操作方式——压栈(push)和弹栈(pop),使得它在计算机科学和软件开发中占据了不可或缺的地位。

        详细讨论了栈在多个领域中的应用。其中,后缀表达式的求值是一个经典的栈应用示例。在这个问题中,我们利用栈来存储操作数,并通过操作数的弹出和结果的压入,实现了表达式的正确计算。这种方法不仅简化了表达式的处理流程,而且提高了计算效率。

        此外,栈还在函数调用、递归等方面发挥着重要作用。在函数调用中,栈用于存储局部变量和返回地址,确保函数能够正确地返回并继续执行。在递归算法中,栈用于保存递归调用的中间结果,从而避免重复计算。

        综上所述,栈作为一种基本而强大的数据结构,在各个领域都有着广泛的应用。通过学习和掌握栈的使用方法和应用场景,我们能够更好地解决实际问题,提高编程效率。

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

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

相关文章

使用 Ollama 和 Open WebUI 自托管 LLM 聊天机器人(无需 GPU)

✨点击这里✨&#xff1a;&#x1f680;原文链接&#xff1a;&#xff08;更好排版、视频播放、社群交流、最新AI开源项目、AI工具分享都在这个公众号&#xff01;&#xff09; 使用 Ollama 和 Open WebUI 自托管 LLM 聊天机器人&#xff08;无需 GPU&#xff09; &#x1f31…

二叉查找树详解

目录 二叉查找树的定义 二叉查找树的基本操作 查找 插入 建立 删除 二叉树查找树的性质 二叉查找树的定义 二叉查找树是一种特殊的二叉树&#xff0c;又称为排序二叉树、二叉搜索树、二叉排序树。 二叉树的递归定义如下&#xff1a; &#xff08;1&#xff09;要么二…

10. MySQL 用户

文章目录 【 1. 权限表 】1.1 user 权限表1.1.1 用户列1.1.2 权限列1.1.3 安全列1.1.4 资源控制列 1.2 db 表用户列权限列 1.3 tables_priv 表1.4 columns_priv 表1.5 procs_priv表 【 2. 用户管理 】2.1 创建用户 CREATE USER2.2 用户的登陆、退出登陆 MySQL退出 MySQL 2.3 重…

pytorch 笔记:pytorch 优化内容(更新中)

1 Tensor创建类 1.1 直接创建Tensor&#xff0c;而不是从Python或Numpy中转换 不要使用原生Python或NumPy创建数据&#xff0c;然后将其转换为torch.Tensor直接用torch.Tensor创建或者直接&#xff1a;torch.empty(), torch.zeros(), torch.full(), torch.ones(), torch.…

4.通用编程概念

目录 一、变量与常量1.1 变量1.2 常量 二、遮蔽三、数据类型3.1 标量类型1. 整型2. 浮点型3. 布尔类型4.字符类型 3.2 复合类型1. 元组2. 数组 四、函数五、语句和表达式六、函数的返回值 一、变量与常量 1.1 变量 在Rust中默认的变量是不可变的&#xff0c;如果修改其值会导致…

【Vue】如何提供访问vuex的数据

文章目录 一、提供数据二、访问Vuex中的数据通过$store访问的语法1&#xff09;模板中使用2&#xff09;组件逻辑中使用3&#xff09;js文件中使用 三、通过辅助函数 - mapState获取 state中的数据 一、提供数据 State提供唯一的公共数据源&#xff0c;所有共享的数据都要统一…

[office] 快速删除excel中的空行和列的方法 #其他#学习方法#经验分享

快速删除excel中的空行和列的方法 用户在网上下载好的Excel表格打开之后发现有很多空白行&#xff0c;怎么样将这些空白行或单元格一次性删除掉呢?下面教大家在Excel中用定位一次性可以把空白行删除 用户在网上下载好的Excel表格打开之后发现有很多空白行&#xff0c;怎么样将…

Vue3 使用audio播放语音+监听播放语音完成事件

需求&#xff1a;输入一段文字&#xff0c;点击语音框&#xff0c;将本地语音&#xff08;提前准备好的&#xff09; 播放出来 播放中效果 代码 <div class"listConAI" click"handleOpenSpeech" ><imgsrc"../../../../assets/images/blueo…

中文文案写作有哪些合适的AIGC工具?

这是计育韬老师第 8 次开展面向全国高校的新媒体技术公益巡讲活动了。而在每场讲座尾声&#xff0c;互动答疑环节往往反映了高校师生当前最普遍的运营困境&#xff0c;特此计老师在现场即兴答疑之外&#xff0c;会尽量选择有较高价值的提问进行文字答疑梳理。 *本轮巡讲主题除了…

【Vue】开启严格模式及Vuex的单项数据流

文章目录 一、引出问题二、开启严格模式 一、引出问题 目标 明确 vuex 同样遵循单向数据流&#xff0c;组件中不能直接修改仓库的数据 这样数据的流向才会更加清晰&#xff0c;将来对数据的修改&#xff0c;都在仓库内部实现的&#xff0c;更易于维护 直接在组件中修改Vuex中…

Spring进阶技巧:利用AOP提前介入的巧妙实践

Spring框架中的面向切面编程&#xff08;AOP&#xff09;是一种强大的机制&#xff0c;它允许开发者在不修改原有代码的情况下&#xff0c;对程序进行横向切面的功能扩展。AOP提供了一种方式&#xff0c;可以在目标Bean的生命周期早期阶段就实施切面逻辑&#xff0c;这为我们在…

【一百一十】【算法分析与设计】[SDOI2009] HH的项链,树状数组应用,查询区间的种类数,树状数组查询区间种类数

P1972 [SDOI2009] HH的项链 [SDOI2009] HH的项链 题目描述 HH 有一串由各种漂亮的贝壳组成的项链。HH 相信不同的贝壳会带来好运&#xff0c;所以每次散步完后&#xff0c;他都会随意取出一段贝壳&#xff0c;思考它们所表达的含义。HH 不断地收集新的贝壳&#xff0c;因此&am…

SMS - 基于阿里云实现手机短信验证码登录(无需备案,非测试)

目录 SMS 环境调试 从阿里云云市场中购买第三方短信服务 调试短信验证码功能 实战开发 封装组件 对外接口 调用演示 SMS 环境调试 从阿里云云市场中购买第三方短信服务 a&#xff09;进入阿里云首页&#xff0c;然后从云市场中找到 “短信” &#xff08;一定要从 云…

如何实现网站HTTPS访问

在当今网络安全至关重要的时代&#xff0c;HTTPS已经成为网站安全的基本标准。HTTPS&#xff08;超文本传输安全协议&#xff09;通过在HTTP协议基础上加入SSL加密层&#xff0c;确保了数据在用户浏览器和服务器之间的传输是加密的&#xff0c;有效防止数据被窃取或篡改&#x…

使用 PAI-DSW x Free Prompt Editing图像编辑算法,开发个人AIGC绘图小助理

教程简述 在本教程中&#xff0c;您将学习在阿里云交互式建模平台PAI-DSW x Free Prompt Editing&#xff08;CVPR2024中选论文算法&#xff09;图像编辑算法&#xff0c;开发个人AIGC绘图小助理&#xff0c;实现文本驱动的图像编辑功能单卡即可完成AIGC图片风格变化、背景变化…

Cesium开发环境搭建(一)

1.下载安装Node.js 进入官网地址下载安装包 Node.js — Download Node.js https://cdn.npmmirror.com/binaries/node/ 选择对应你系统的Node.js版本&#xff0c;这里我选择的是Windows系统、64位 安装完成后&#xff0c;WINR&#xff0c;输入node --version&#xff0c;显示…

React + SpringBoot实现图片预览和视频在线播放,其中视频实现切片保存和分段播放

图片预览和视频在线播放 需求描述 实现播放视频的需求时&#xff0c;往往是前端直接加载一个mp4文件&#xff0c;这样做法在遇到视频文件较大时&#xff0c;容易造成卡顿&#xff0c;不能及时加载出来。我们可以将视频进行切片&#xff0c;然后分段加载。播放一点加载一点&am…

tcp aimd 窗口的推导

旧事重提&#xff0c;今天用微分方程的数值解观测 tcp aimd 窗口值。 设系统 AI&#xff0c;MD 参数分别为 a 1&#xff0c;b 0.5&#xff0c;丢包率由 buffer 大小&#xff0c;red 配置以及线路误码率共同决定&#xff0c;设为 p&#xff0c;窗口为 W&#xff0c;则有&…

HAL STM32F1 通过查表方式实现SVPWM驱动无刷电机测试

HAL STM32F1 通过查表方式实现SVPWM驱动无刷电机测试 &#x1f4cd;相关篇《基于开源项目HAL STM32F4 DSP库跑SVPWM开环速度测试》 ✨针对STM32F1系列&#xff0c;没有专门的可依赖的DSP库&#xff0c;为了实现特定函数的浮点运算快速计算&#xff0c;通过查表方式来实现&#…

番外篇 | 利用华为2023最新Gold-YOLO中的Gatherand-Distribute对特征融合模块进行改进

前言:Hello大家好,我是小哥谈。论文提出一种改进的信息融合机制Gather-and-Distribute (GD) ,通过全局融合多层特征并将全局信息注入高层,以提高YOLO系列模型的信息融合能力和检测性能。通过引入MAE-style预训练方法,进一步提高模型的准确性。🌈 目录 🚀1.论文解…