表达式求值(综合应用的难题)

一、各种表达式的含义与操作

请看下面链接里面的博客吧,这是一位大佬写的,里面的图很是不错,可以看看。

各种表达式的概念与操作

二、题目

给定一个表达式,其中运算符仅包含 +,-,*,/(加 减 乘 整除),可能包含括号,请你求出表达式的最终值。

注意:

  • 数据保证给定的表达式合法。
  • 题目保证符号 - 只作为减号出现,不会作为负号出现,例如,-1+2,(2+2)*(-(1+1)+2) 之类表达式均不会出现。
  • 题目保证表达式中所有数字均为正整数。
  • 题目保证表达式在中间计算过程以及结果中,均不超过 2^{31}-1
  • 题目中的整除是指向 0取整,也就是说对于大于 0的结果向下取整,例如 5/3=1,对于小于 0的结果向上取整,例如 5/(1−4)=−1。
  • C++和Java中的整除默认是向零取整;Python中的整除//默认向下取整,因此Python的eval()函数中的整除也是向下取整,在本题中不能直接使用。

输入格式

共一行,为给定表达式。

输出格式

共一行,为表达式的结果。

数据范围

表达式的长度不超过 10^{5}

输入样例:

(2+2)*(1+1)

输出样例:

8

 

/*这个题是中缀表达式的存储与计算,至于什么是中缀表达式,前缀表达式,后缀表达式,就看下面链接里面的
博客吧。
*/
#include<iostream>
#include<stack>
#include<algorithm>
#include<unordered_map>//要使用哈希表,就要加这个。using namespace std;stack<int> num;
stack<char> op;
//建立两个栈,一个是专门存数字字符的,一个是专门存储符号的。
void eval(){auto b=num.top();  num.pop();auto a=num.top();  num.pop();auto c=op.top();   op.pop();int x;if(c=='+')  x=a+b;if(c=='-')  x=a-b;if(c=='*')  x=a*b;if(c=='/')  x=a/b;/*这个eval()函数是执行的弹出栈并执行计算的操作,为什么要先b再a呢,这是因为栈的原因,先进后出的性质,导致了这样,至于具体是怎么样,可以看下面链接里面的那篇博客的图解,那是一位大佬分析的,很是不错的。*/num.push(x);/*每一步都能得到一个结果,将得到的结果压入num栈中。*/
}int main(){unordered_map<char,int> pr{{'+',1},{'-',1},{'*',2},{'/',2}};//用哈希表来定义各个运算符的优先级,这里要记得在开头调用有这个函数的开头。string str;cin>>str;for(int i=0;i<str.size();i++){auto c=str[i];//遍历这个字符串里面的每一个字符if(isdigit(c)){//注意这里的isdigit(),这个是判断一个字符是不是数字字符的一个函数吧。int x=0,j=i;while(j<str.size()&&isdigit(str[j]))x=x*10+str[j++]-'0';/*这里是容易疏忽的一点,虽然样例输入的都是一位数的运算,但很有可能还有多位数,所以这个x其实就是求连续的数字字符的大小,获得其值*/i=j-1;//这个i=j-1很是重要,对我来说的话,下面会有详细的解释的。num.push(x);//得到了连续数字字符的结果后就入栈num。}else if(c=='(')  op.push(c);//如果检测到了左括号直接入op栈就可以。else if(c==')'){while(op.top()!='(')  eval();/*如果检测到了是右括号的话,那右括号不用入栈,直接在现在的op栈里面操作,一直输出和计算,直到找到左括号为止,就停止操作了。*/op.pop();/*这个pop是操作到了左括号,表示左右括号之间的运算符已经全部操作完,这个时候直接将op栈里面的左括号弹出栈就行,因为这个时候已经没用了。*/}else{while(op.size()&&op.top()!='('&&pr[c]<=pr[op.top()])eval();/*这是另一种情况,当栈里面没有任何的括号时,就只是那几个操作符的话,那么就是判断优先级,只要这个op栈不为空且里面没有括号且此时c的符号优先级比之前的栈顶的符号优先级低的话,那么就会先把原来栈里面的运算符一直出栈并运算,直到找到比现在c这个字符优先级高的为止,加入说都是同级的,那么就是把栈里面所有的运算的字符全部都出栈并操作,栈空以后才将c这个字符运算符给插进去,就很完美了,至于这个为什么优先级这么想,具体的表现的话,我会在下面画一张图,看了应该就能理解。*/op.push(c);/*这个op的入栈就是最后我说的那个了,要么都是同级的运算符,栈空入栈;要么就是c的优先级本来就比原来栈顶的优先级高,直接入栈,这是这句代码的含义,要注意。*/}}while(op.size())  eval();/*如果说因为各种各样的操作以后,op符号栈里面还有运算符,那就把他们全部都弹出并计算,直至栈空。*/cout<<num.top()<<endl;/*最后各种各样的操作完成后,最后得到的num栈里面就只有一个元素了,也就是那个最后的结果,所以直接输出num.top()就行。*/return 0;
}

 

代码详细解释

1. 引入头文件
#include <iostream>
#include <cstring>
#include <algorithm>
#include <stack>
#include <unordered_map>
  • #include <iostream>: 引入输入输出流库,用于标准输入输出。
  • #include <cstring>: 引入C风格字符串处理库,这里其实不需要用到。
  • #include <algorithm>: 引入算法库,这里也没有使用。
  • #include <stack>: 引入栈的实现库。
  • #include <unordered_map>: 引入无序映射(哈希表)库,用于存储操作符优先级。

 

2. 定义栈
stack<int> num;
stack<char> op;
  • stack<int> num;: 整数栈,用于存储操作数。
  • stack<char> op;: 字符栈,用于存储操作符和括号。

3. eval 函数
void eval()
{auto b = num.top(); num.pop();auto a = num.top(); num.pop();auto c = op.top(); op.pop();int x;if (c == '+') x = a + b;else if (c == '-') x = a - b;else if (c == '*') x = a * b;else x = a / b;num.push(x);
}
  • auto b = num.top(); num.pop();: 从 num 栈中取出顶层元素(右操作数)并移除它。
  • auto a = num.top(); num.pop();: 从 num 栈中取出顶层元素(左操作数)并移除它。
  • auto c = op.top(); op.pop();: 从 op 栈中取出顶层操作符并移除它。
  • int x;: 定义一个变量 x 用于存储计算结果。
  • if (c == '+') x = a + b;: 根据操作符 c 执行相应的计算,并将结果存入 x
  • num.push(x);: 将计算结果 x 压入 num 栈中。

4. main 函数
int main()
{unordered_map<char, int> pr{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};string str;cin >> str;for (int i = 0; i < str.size(); i ++ ){auto c = str[i];if (isdigit(c)){int x = 0, j = i;while (j < str.size() && isdigit(str[j]))x = x * 10 + str[j ++ ] - '0';i = j - 1;num.push(x);}else if (c == '(') op.push(c);else if (c == ')'){while (op.top() != '(') eval();op.pop();}else{while (op.size() && op.top() != '(' && pr[op.top()] >= pr[c]) eval();op.push(c);}}while (op.size()) eval();cout << num.top() << endl;return 0;
}
  • unordered_map<char, int> pr{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};: 创建一个哈希表 pr 用于存储操作符的优先级,* 和 / 的优先级高于 + 和 -
  • string str; cin >> str;: 从标准输入读取一个字符串 str
  • for (int i = 0; i < str.size(); i++ ): 遍历字符串中的每个字符。
    • auto c = str[i];: 获取当前字符。
    • if (isdigit(c)): 如果当前字符是数字:
      • int x = 0, j = i;: 定义一个变量 x 用于构建完整的数字,并初始化 j 为当前索引。
      • while (j < str.size() && isdigit(str[j])): 读取完整的数字,直到字符不是数字为止。
      • x = x * 10 + str[j++] - '0';: 逐步构建数字。
      • i = j - 1;: 更新 i 以跳过已读取的数字部分。
      • num.push(x);: 将读取到的数字压入 num 栈中。
    • else if (c == '(') op.push(c);: 如果当前字符是左括号,将其压入 op 栈。
    • else if (c == ')'): 如果当前字符是右括号:
      • while (op.top() != '(') eval();: 不断执行 eval 直到遇到左括号。
      • op.pop();: 弹出左括号。
    • else: 如果当前字符是操作符:
      • while (op.size() && op.top() != '(' && pr[op.top()] >= pr[c]) eval();: 根据操作符优先级执行 eval,直到栈顶的操作符优先级低于当前操作符。
      • op.push(c);: 将当前操作符压入 op 栈中。
  • while (op.size()) eval();: 执行所有剩余的操作。
  • cout << num.top() << endl;: 输出结果。

实际例子

假设输入字符串是 "3+(2*2)"

  1. 初始状态:

    • num: 空
    • op: 空
  2. 读取 '3':

    • num3
    • op: 空
  3. 读取 '+':

    • num3
    • op+
  4. 读取 '(':

    • num3
    • op+ (
  5. 读取 '2':

    • num3 2
    • op+ (
  6. 读取 '*':

    • num3 2
    • op+ ( *
  7. 读取 '2':

    • num3 2 2
    • op+ ( *
  8. 读取 ')':

    • 计算 2 * 2
      • num3 4
      • op+
    • 计算 3 + 4
      • num7
      • op: 空

最终输出 7

 

三、对于里面i=j-1的理解

代码中的 i = j - 1

代码的关键部分如下:

for (int i = 0; i < str.size(); i++) {if (isdigit(str[i])) {int x = 0, j = i;while (j < str.size() && isdigit(str[j])) {x = x * 10 + str[j++] - '0';}// 跳过已经处理过的数字部分i = j - 1;}// 处理其他字符,如 '+', '*', '(', ')'
}

 在这个代码中,i = j - 1 的作用是更新 i 的值,以便在下一次 for 循环时,从处理完数字后的下一个位置继续进行。

去掉 i = j - 1 的影响

如果去掉 i = j - 1i 的值不会被更新到数字解析后的下一个位置。这可能导致一些问题:

  1. 重复处理

    • 当 for 循环进入到 i 的位置时,它会重新开始处理已经解析过的部分。这意味着原本已经被处理的部分(比如 "37")会被重复处理,因为 i 的值没有被更新。
  2. 无限循环问题

    • 如果 i 从未更新,for 循环会一直从原始位置开始,例如位置 0。因此,如果 i 从未跳过已处理的部分,循环会不断重新处理这些部分,从而导致无限循环。

去掉i=j-1后的代码

for (int i = 0; i < str.size(); i++) {if (isdigit(str[i])) {int x = 0, j = i;while (j < str.size() && isdigit(str[j])) {x = x * 10 + str[j++] - '0';}// 此处如果没有 i = j - 1,i 将保持原来的位置}// 处理其他字符,如 '+', '*', '(', ')'
}

 

假设我们解析了 "37" 并且 j 在此时是 2,如果我们没有 i = j - 1,那么 i 会继续增加而没有跳过已处理的部分:

  1. 当前 i 的值

    • i 仍然是循环的当前值,比如 0 或 1
  2. 下一次迭代

    • 在下一次 for 循环中,i 可能会从未处理的部分开始(如 2),但由于未更新 i,循环可能会再次处理已经解析的部分。

缺少i=j-1后的代码示例分析

假设你的字符串是 "37+8*(9-4)",且初始 i0

  1. i0 时,我们解析了 "37" 并更新 j2,此时没有 i = j - 1i 继续从 0 开始。

  2. 因此,for 循环将再次处理 0 位置的字符 '3''7',从而重复解析这些字符。

总结来说,i = j - 1 是用来跳过已经处理过的部分,确保 for 循环从未处理的部分开始。如果没有更新 ifor 循环会重复处理相同的部分,可能导致无限循环或错误的结果。

这里要注意一点,就是在判断连续的数字字符后,遍历到了运算符字符后,for循环里面的循环体,从内到外也就是while,if等语句都会退出,这个时候虽然不满足循环条件了,要退出当前的for循环,那么for循环最后的那个i++也是要执行的,只不过后面不会再有机会i++了而已,除非又遇到了连续数字字符,详细点来说就是以下的描述:

i++for 循环中确实总是会执行,但如何执行以及最终的效果会受到循环体内其他逻辑的影响。

详细分析

for 循环的结构中:

for (initialization; condition; increment) {// loop body
}

 

  • 初始化initialization 只在第一次迭代之前执行一次。
  • 条件: 在每次迭代之前评估,如果条件为 true,则执行循环体。
  • 增量: 每次循环体执行完后,increment(如 i++)都会被执行。

i++ 的执行

即使在循环体内你做了其他操作,i++ 这个增量操作在每次循环结束时都会执行。例如:

for (int i = 0; i < str.size(); i++) {auto c = str[i];if (isdigit(c)) {int x = 0, j = i;while (j < str.size() && isdigit(str[j]))x = x * 10 + str[j++] - '0';num.push(x);i = j - 1; // 更新 i,以跳过已处理的数字}// 其他逻辑
}

 

在这个例子中:

  • 如果有 i = j - 1: 这行代码更新 i 的值,使得在下一次循环开始时 i 跳过了已经处理过的数字。因此,i++ 实际上在这里会使 i 增加到 j,但是由于 i 被设置为 j - 1,下一次循环开始时 ij - 1 开始执行,所以 i++ 使得 i 正确地推进到下一个未处理的字符位置。

  • 如果没有 i = j - 1: 在这种情况下,i 会在数字处理完后被默认递增。这样,如果你在处理数字后没有更新 i,下一个迭代会从上一个字符开始,这样可能会导致错误的重复处理。此时,i++ 会将 i 增加到下一个位置,但由于没有正确跳过已处理的数字,可能会导致重复遍历。

结论

  • i++ 总是会执行,但最终 i 的值取决于循环体内的其他逻辑。
  • i = j - 1: 在这种情况下,i 被设置为处理过的数字的末尾减去 1,确保 i 从正确的位置继续遍历,避免了重复处理的问题。

在没有 i = j - 1 的情况下,i++ 会导致重复处理字符;而有了 i = j - 1,可以确保跳过已经处理的部分,防止重复遍历。

四,关于优先级的图

 

  从上面的图就可以看出来,这个题是用了很多知识:栈的存储与应用,二叉树的中序遍历,哈希表的使用,连续数字字符的获取,for循环的使用等等,所以一定要着重的注意这个题,真的很经典也很难。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

产业报告 | 2024年中国机器人产业研究报告

近日&#xff0c;世界机器人大会在北京亦庄国际会展中心举办。据悉&#xff0c;这是国内最大的机器人展会&#xff0c;今年的展会规模更是创下新高&#xff0c;共有169家企业参展&#xff0c;展出的产品数量超过600款&#xff0c;观展人次超过30万&#xff0c;足见各行各业对机…

QT widgets 窗口缩放,自适应窗口大小进行布局

1. 窗口布局 2. 尺寸策略&#xff1a;扩展 Fixed (固定): 行为&#xff1a;控件的大小是固定的&#xff0c;不会随着窗口大小的变化而改变。它的大小由控件的 sizeHint() 返回的值决定。 适用场景&#xff1a;当你希望控件的大小保持不变&#xff0c;不随布局调整时使用&#x…

前端vue-插值表达式和v-html的区别

创建vue实例的时候&#xff0c;可以有两种形式。 1.let appnew Vue({}) 2 const appnew Vue({}) 3 el是挂载点&#xff0c;是上面div的id值 4 data中的值可以展示在上面div中 5 v-html标签里面如果有内容&#xff0c;则我们的新内容会把标签里面的内容覆盖掉

解决 Torch not compiled with CUDA enabled 问题 | MiniCPM3-4B 【应用开发笔记】

最近在研究测试MiniCPM3-4B&#xff0c;这里记录一下遇到的cuda和torch版本问题 在调试和运行MiniCPM3-4B过程中如果出现找不到某个包&#xff0c;就用pip进行安装&#xff0c;如果提示GPU相关的问题则需要进一步检查 解决 Torch not compiled with CUDA enabled 问题 一、查看…

Arthas 全攻略:让调试变得简单

文章目录 一、简介二、命令列表 一、简介 注意 &#xff1a; 我安装的版本是&#xff1a;Arthas V3.7.2 官网&#xff1a;https://arthas.aliyun.com/doc/ 相关错误解决方案请看GitHub&#xff1a;https://github.com/alibaba/arthas/issues Alibaba开源的Java诊断工具。 从…

我的AI工具箱Tauri版-MicrosoftTTS文本转语音

本教程基于自研的AI工具箱Tauri版进行MicrosoftTTS文本转语音服务。 MicrosoftTTS文本转语音服务 是自研的AI工具箱Tauri版中的一款功能模块&#xff0c;专为实现高效的文本转语音操作而设计。通过集成微软TTS服务&#xff0c;用户可以将大量文本自动转换为自然流畅的语音文件…

圣多纳释放法,达到内心的平静

圣多纳释放法的关键在于&#xff1a;我们被情绪控制时&#xff0c;不应该压抑情绪或是发泄情绪。 利用释放法处理情绪是最健康的方法&#xff0c;可以帮助我们获得自由与平静。当我们面对讨厌的人时&#xff0c;我们真正要做的并非压抑或者爆发&#xff0c;而是将“讨厌”这种…

仪表放大器AD620

AD623 是一款低功耗、高精度的仪表放大器&#xff0c;而不是轨到轨运算放大器。它的输入电压范围并不覆盖整个电源电压&#xff08;轨到轨&#xff09;&#xff0c;但在单电源供电下可以处理接近地电位的输入信号。 AD620 和 AD623 都是仪表放大器&#xff0c;但它们在一些关键…

HTB-Netmon(prtg配置文件获取,CVE-2018-9276复现)

前言 各位师傅大家好&#xff0c;我是qmx_07&#xff0c;今天给大家讲解Netmon靶机 渗透流程 信息搜集 服务器开放了80HTTP、21FTP(匿名登录)、445SMB服务 FTP匿名登录 获取敏感文件 登录后台 网站登录需要 账号、密码 &#xff0c;尝试去FTP服务 碰下运气 通过翻阅ft…

基于Python flask的淘宝商品数据分析可视化系统,包括大屏和主题分析,还有回归预测

背景介绍 随着电子商务的迅猛发展&#xff0c;平台上积累了大量的用户行为和商品交易数据。这些数据蕴含着极大的商业价值&#xff0c;可以为市场趋势预测、商品优化以及用户行为分析提供重要的参考。淘宝作为全球最大的在线购物平台之一&#xff0c;拥有海量的商品和用户数据…

联想一体机怎么重装系统_联想一体机重装win10系统教程

联想一体机怎么重装系统&#xff1f;联想一体机重装系统有很多&#xff0c;有一键重装、有U盘重装、有硬盘重装等方式&#xff0c;最保险的方式是u盘重装系统。需要准备一个空U盘&#xff0c;然后利用第三方工具制作启动u盘&#xff0c;制作完成后进入pe重装系统&#xff0c;下…

集装箱机房可视化:高效管理与监控

通过图扑可视化平台实时监控集装箱机房的运行状态和环境参数&#xff0c;优化资源配置&#xff0c;提升运维效率&#xff0c;确保数据中心安全可靠运行。

Swagger 概念和使用以及遇到的问题

前言 接口文档对于前后端开发人员都十分重要。尤其近几年流行前后端分离后接口文档又变 成重中之重。接口文档固然重要,但是由于项目周期等原因后端人员经常出现无法及时更新&#xff0c; 导致前端人员抱怨接口文档和实际情况不一致。 很多人员会抱怨别人写的接口文档不…

dll注入的实现及session0注入

记录一下跟着红队蓝军师傅学免杀的过程 本节旨在学习dll注入和代码实现并不涉及免杀知识 dll注入流程 dll注入要么注入自己写的程序要么找个程序进行注入&#xff0c;一般是找其他程序进行注入 所以按照上面的步骤进行 其中申请空间&#xff0c;创建线程都是在远程的另一个进…

【Linux】-----进程第一弹

目录 概念 描述进程-PCB 查看进程 获取进程标识符 终止进程 fork创建进程 返回值说明 进程的状态 ①运行状态(R) ②浅度睡眠(S) ③深度睡眠(D) ④暂停状态(T) ⑤僵尸状态(Z)(重点) 是什么&#xff1f; 举例 危害 孤儿进程 ⑥死亡状态(X) 概念 课本上对于进程…

土豆王国小乐队携手阿派朗创造力乐园,打造2024年okgo儿童音乐节

艺术与科技的完美融合&#xff0c;为首都少年儿童带来音乐盛宴 北京&#xff0c;2024年9月19日 —— 备受期待的2024年okgo儿童音乐节即将于9月21日至22日在北京阿派朗创造力乐园盛大开幕。这场由土豆王国小乐队与阿派朗创造力乐园联合举办的音乐节&#xff0c;旨在为首都及全国…

【828华为云征文|华为云Flexus X实例部署指南:轻松搭建可道云KODBOX项目】

文章目录 华为云 Flexus X 实例&#xff1a;引领高效云服务的新时代部署【可道云KODBOX】项目准备工作具体操作指南服务器环境确认宝塔软件商店操作域名解析可道云KODBOX登录页效果验证 总结 华为云 Flexus X 实例&#xff1a;引领高效云服务的新时代 在云计算领域&#xff0c…

基于Ubuntu22.04的cups安装与配置

目录 关于cups 关于cups Linux中的CUPS(Common UNIX Printing System,通用UNIX打印系统)是一个开源的打印系统,它提供了一套完整的管理打印设备、实现可靠打印和网络打印的方案。 Cups安装与与配置 1、升级系统 sudo apt update -y && sudo apt upgrade -y 2、安…

代码随想录算法训练营43期 | Day 20 —— 235.二叉搜索树的最近公共祖先、701.二叉搜索树中的插入操作、450.删除二叉搜索树中的节点

代码随想录算法训练营 代码随想录算法训练营43期235.二叉搜索树的最近公共祖先701.二叉搜索树中的插入操作450.删除二叉搜索树中的节点 代码随想录算法训练营43期 235.二叉搜索树的最近公共祖先 解题思路&#xff1a; 二叉搜索树一定是有序的 判断条件&#xff1a; cur>p &…

MySQL索引知识个人笔记总结(持续整理)

本篇笔记是个人整理的索引知识总结&#xff0c;刚开始有点乱&#xff0c;后续会一直边学边整理边总结 索引&#xff08;index&#xff09;是帮助MySQL高效获取数据的数据结构(有序)。就好比索引就是数据的目录 索引结构 Btree索引,Hash索引,Full-text索引&#xff0c;R-tree(空…