算法:求表达式的值的通用解法框架

对于一个表达式.我们通常的讲包含两个部分,运算数(数字)和运算符

如: 3 + 5 * 7 其中运算符为 +,* ,数字为3,5,7;

如: 0&1&(1|0), 其中运算符从左往右依次为:&&(|),运算数为0110

而,在求解表达式的值的过程中,算法往往采用两个栈来维护.一个用来存储运算数的num栈,另一个用来存储运算符的op栈.

第一步读入数据

表达式读入.通常以字符串存储.

再尔遍历字符串, 对字符串的各个字符进行操作

这里不考虑表达式的合法性.因为若是求表达式的值,考题的重点在于求值的逻辑,而不在于表达式的合法性,99%的情况下,表达式都是合理的 表达式是否合理, 主要判断是括号的匹配, 以及双目运算符的左右是否各自都是数值.

第二步得到数字和运算符

得到数字

在遍历的过程中, 如果遇到了数字,记得连续选取.这里面先介绍两个工具.

double stod(string s)将字符串转成浮点数

C++ 复制代码
 cout << stod("123.5") << endl;//123.5 得到浮点数123.5
 cout << stod("000.02") << endl;//0.02 去掉前导0
 cout << stod("0.23dd") << endl;//0.23 遇到不符合浮点数的字符自动结束

int stoi(string s) 将字符串转成整型

C++ 复制代码
 cout << stoi("123.5") << endl;//123
 cout << stoi("002") << endl;//2
 cout << stoi("327FD32") << endl;//327

string to_string(int/double/long long) 将数字转成字符串

c 复制代码
     cout <<to_string(3.24) << endl;//3.240000 这里要注意浮点数的精度后面的0会被转成小数
     cout <<to_string(1233) << endl;//1233

通过这数字转成字符串和字符串转成数字, 可以更方便的一次性提取字符串中数字的部分.

运算符

运算符通常一个字符.但一个运算符,若占用字符粗中的多个字符,也如同选择运算数一样 ,一并全部提取出来,直接放入op栈中.另外放入栈中用一个简写的字母代替即可.因为op栈中的每个元素都是一个字符例如, 运算符为MAX那么栈中就用一个A来表示该运算.

第三步遍历逻辑

  • 当遇到数字的时候, 将数字如栈num.

  • 当遇到运算符的时候,要开始做运算,

    • 当运算符栈中没有其他元素,即该运算栈为空, 此运算符入栈.

      例如表达式为 3+4*5-7

    遍历到+时,栈中的情况是这样的

那么此时+只能入栈.形成

当遇到数字4的时候

注意遇到数字不做运算,因为你现在得到的是3+44后面如题意要去*5所以无论何时遇到数字都不做运算, 是因为无法保证数字后面是否还有运算符,又或者后面的运算符优先级更高.

遍历到*的时候,这时候因为*的运算符比栈顶的元素要高,所*入栈,因为最终是*的两个因子,左边的是4,右边的还没有输入,所以到这一步*不能操作. 那这里为什么不清栈顶元素呢. 因为如果清了栈顶的元素, 那么会先运算3 + 4, 但实际上我们知道要先算4*5.那什么时候可以清,不急 我们继续往下看..所以这一笔也只是*先入栈如下:

遇到数字5继续入栈;依然不参与运算

这时候有的同学会思考, 咦不是5*4都出现了吗.这里面我们要考虑代码逻辑的普遍性, 如果每次数字出现了都要去特判,那么逻辑会变得很复杂.

接下来遇到-号:

这时候,核心的逻辑来了因为运算符栈里面有元素, 并且是*,那么因为*号运算符的优先级比-高,而-的出现, 代表着前面的*的两个乘数都出现了.那么这时候,就可以处理*了,于是乎处理如下:

注意这里第一个出栈5是第二个乘数,第二个出栈的4是第一个乘数 .这个顺序是不能乱的, 因为如果是÷,-,

a-bb-a可是完全不一样的结果.

这时候op栈中的元素是+,因为+运算符优先级不会低于- 所以,现在的可以把op运算符中的+清掉,逻辑和上一次清栈中*逻辑是一致的.

此时,运算符栈中已经没有元素了.所以-入栈.

再由数字7入栈, 此时,字符串遍历结束.剩余的工作便是清栈了.

再次做出总结

  • 当遇到数字的时候, 将数字如栈num.

  • 当遇到运算符的时候,要开始做运算,

    • op栈为空的时候或者栈顶运算符的优先级低于 遍历的运算符时,直接入栈
    • op栈顶的运算符优先级高于等于遍历的运算符时,则一直清栈,直到上述情况.此时再入栈.

    最后清栈,num栈顶元素为答案.

    核心代码框架

scss 复制代码
 string s;
 cin >> s;
 stack<运算数> num;
 stack<char> op;
 for(auto &ch : s){//遍历字符串
     if(ch是个数字){
         num.push(ch->转成数字);
     }else if(ch 是运算符){
         while(!op.empty() && ch的优先级 <= op.top()的优先级){
             清栈操作();
         }
         op.push(ch运算符)
     }
     
 }
 清栈操作();
 cout<< num.top() << endl;

上面的清栈操作, 代码中只做一次运算,根据不同的题意,和不同的运算符,使用对应的逻辑. 运算符的优先级,也看题意,大多数符合正常的运算规则,但不排除题目中有一些特定的运算符或者函数(函数也当做运算符)

括号的处理

括号实际上不算是运算符.他的存在只是帮助我们更好的切割一个整体.在这里我们把左括号和右括号分开处理.

左括号

当做运算符来处理,并且他的优先级是最低的.并且入op栈中,用来挡住清栈运算.如案例`3 * (8-4) + 2:

当遍历到-的时候,因为(的优先级是最低的,所以这时候-不做任何操作直接入栈, 避免清栈去运算3*8;

右括号

当右括号出现, 说明表达式一个整体的部分到此结束. 那么我们一直清栈,直到遇到(为止.然后在单独的把(出栈.

)不用入op栈.

完善整体的代码逻辑如下:

C++ 复制代码
     string s;
     cin >> s;
     stack<运算数> num;
     stack<char> op;
     for(auto &ch : s){//遍历字符串
         if(ch是个数字){
             num.push(ch->转成数字);
         }else if(ch =='('){
             '('->入op栈;
         }else if(ch == ')'{
             while(op.empty() != '('){
                 清栈操作();
             }
             op.pop(); //把'('弹掉
         }else if(ch 是运算符){
             while(!op.empty() && ch的优先级 <= op.top()的优先级){
                 清栈操作();
             }
             op.push(ch运算符)
         }
     }
     清栈操作();
     cout<< num.top() << endl;

举例一

路飞想做一个计算器,但是,他要做的不仅仅是一计算一个A+B 的计算器,他想实现随便输入一个表达式都能求出它的值的计算器,现在请你帮助他来实现这个计算器吧。

比如输入:"1+2/4=",程序就输出 1.50(结果保留两位小数)

输入格式

第一行输入一个整数 n,共有 n 组测试数据(n≤20)。

每组测试数据只有一行,是一个长度不超过 1000的字符串,表示这个运算式,每个运算式都是以 "=" 结束。这个表达式里只包含 +-*/ 与小括号这几种符号。其中小括号可以嵌套使用。数据保证输入的操作数中不会出现负数。

数据保证除数不会为0

输出格式

每组都输出该组运算式的运算结果,输出结果保留两位小数。

输出时每行末尾的多余空格,不影响答案正确性

样例输入 1

ini 复制代码
 2
 1.000+2/4=
 ((1+2)*5+1)/4=

样例输出 1

 1.50
 4.00
C++ 复制代码
     #include <iostream>
     #include <stack>
     #include <iomanip>
     ​
     using namespace std;
     stack<char>op;
     stack<double> num;
     double yunsuan(double x, double y, double op){//运算规则
     //     cout << "x = " << x << " y = " << y << endl;
         if(op == '+') return x + y;
         if(op == '-') return y - x; 
         if(op == '*') return x * y;
         if(op == '/') return y / x;
     }
     bool isOp(char ch){
         return ch == '+' || ch == '-' || ch == '*' || ch == '/';
     }
     int opLe(char ch){//定义运算符的优先级
         if(ch == '(') return 0;
         if(ch == '+' || ch == '-') return 1;
         if(ch == '*' || ch == '/') return 2;
     }
     void opOnce(){//清栈操作 清栈一次
         double d1 = num.top();
         num.pop();
         double d2 = num.top();
         num.pop();
         char oo = op.top();
         op.pop();
     //     cout << d1 << ' ' <<  d2 << ' ' << oo << endl;
         num.push(yunsuan(d1,d2,oo));
     }
     double solve(string s){
         for(int i = 0; i < s.size() - 1; i ++){
             if(s[i] >='0' && s[i] <= '9'){
                 string t = s.substr(i);
                 double f = stod(t);
                 num.push(f);
                 while(s[i] >='0' && s[i] <= '9' || s[i] == '.'){
                     i++;
                 }
                  i -= 1;
             }else if(s[i] == '('){
                 op.push('(');
             }else if(s[i] == ')'){
                 while(op.top() != '('){
                     opOnce();//做一次运算
                 }
                 op.pop();
             }else{
                 while(!op.empty() && opLe(op.top()) >= opLe(s[i])){
                     opOnce();//做一次运算
                 }
                 op.push(s[i]);
             }
         }
         while(!op.empty()){
             opOnce();//做一次运算
         }
         return num.top();
     }
     int main(){
         int t;
         cin >> t;
         cout << fixed << setprecision(2);
         while(t--){
             string s;
             cin >> s;
             while(!op.empty()) op.pop();
             while(!num.empty()) num.pop();
     //         verify(s);
             cout << solve(s) << endl;
         }
         
         
         return 0;
     }

举例二

假设表达式定义为:

  1. 一个十进制的正整数 X 是一个表达式。
  2. 如果 X 和 Y 是 表达式,则 X+Y, X*Y 也是表达式; *优先级高于+.
  3. 如果 X 和 Y 是 表达式,则 函数 Smax(X,Y)也是表达式,其值为:先分别求出 X ,Y 值的各位数字之和,再从中选最大数。
  4. 如果 X 是 表达式,则 (X)也是表达式。

例如:

表达式 12*(2+3)+Smax(333,220+280) 的值为 69。

请你编程,对给定的表达式,输出其值。

输入格式

第一行: T 表示要计算的表达式个数 (1≤T≤10)

接下来有 T 行, 每行是一个字符串,表示待求的表达式,长度≤1000

输出格式

对于每个表达式,输出一行,表示对应表达式的值。

数据范围

1≤n≤1000

ops[i] 为 "C""D""+",或者一个表示整数的字符串。整数范围是 [−3×10^4,3×10^4]

对于 "+" 操作,题目数据保证记录此操作时前面总是存在两个有效的分数

对于 "C""D" 操作,题目数据保证记录此操作时前面总是存在一个有效的分数

输出时每行末尾的多余空格,不影响答案正确性

样例输入 1

scss 复制代码
 3
 12+2*3
 12*(2+3)
 12*(2+3)+Smax(333,220+280)

样例输出 1

 18
 60
 69
C++ 复制代码
     #include <iostream>
     #include <stack>
     using namespace std;
     ​
     stack<char> op;
     stack<int> num;
     int opLe(char ch){//定义运算符的优先级
         if(ch == '(') return 0;
         if(ch == ',') return 1;
         if(ch == '+' || ch == '-') return 2;
         if(ch == '*' || ch == '/') return 3;
     //     if(ch == 'S') return 4;
     }
     int getSum(int x){//获得一个数的各个位的和
         int ans = 0;
         while(x){
             ans += x % 10;
             x /= 10;
         }
         return ans;
     }
     void opOnce(){//清栈运算一
         char oo = op.top();
         int y = num.top();
         num.pop();
         int x = num.top();
         num.pop();
         if(oo == '+'){
             num.push(x + y);
         }else if(oo == '-'){
             num.push(x - y);
         }else if(oo == '*'){
             num.push(x * y);
         }else if(oo == '/'){
             num.push(x / y);
         }else if(oo == ','){
             int tx = getSum(x);
             int ty = getSum(y);
             num.push(max(tx,ty));
         }
         op.pop();
     }
     int solve(string s){//主体框架
         for(int i = 0; i <s.size(); i ++){
             if(s[i] >= '0' && s[i] <= '9'){
                 string t = s.substr(i);
                 int n = stoi(t);
                 num.push(n);
                 int jump = to_string(n).size();
                 //1234+
                 i += jump - 1;
             }else if(s[i] == '('){
                 op.push('(');
             }else if(s[i] == ')'){
                 while(op.top() != '('){
                     opOnce();
                 }
                 op.pop();
             }else if(s[i] == 'S'){
                 i+=3;
             }else{//+-*/,
                 while(! op.empty() && opLe(op.top()) >= opLe(s[i])){
                     opOnce();
                 }
                 op.push(s[i]);
             }
         }
         while(!op.empty()){
             opOnce();
         }
         return num.top();
     }
     int main(){
         int n;
         cin >> n;
         while(n--){
             string s;
             cin >> s;
             while(!op.empty()) op.pop();
             while(!num.empty()) num.pop();
             cout << solve(s) << endl;
         }
         return 0;
     }

举例三

逻辑表达式是计算机科学中的重要概念和工具,包含逻辑值、逻辑运算、逻辑运算优先级等内容。

在一个逻辑表达式中,元素的值只有两种可能:00(表示假)和 1 (表示真)。元素之间有多种可能的逻辑运算,本题中只需考虑如下两种:"与"(符号为&)和"或"(符号为|)。其运算规则如下:

0&0=0 ,0&1 = 0, 1&0 = 0, 1&1 = 1

0|0 = 0 , 0|1 = 1, 1|0 = 1 , 1|1 = 1;

在一个逻辑表达式中还可能有括号。规定在运算时,括号内的部分先运算;两种运算并列时,& 运算优先于 | 运算;同种运算并列时,从左向右运算。

比如,表达式 0∣1&00∣1&0 的运算顺序等同于 0∣(1&0)0∣(1&0) ;表达式 0&1&0∣10&1&0∣1 的运算顺序等同于 ((0&1)&0)∣1((0&1)&0)∣1

此外,在 C++ 等语言的有些编译器中,对逻辑表达式的计算会采用一种"短路"的策略。:在形如 a&b 的逻辑表达式中,会先计算a 部分的值,如果 a=0,那么整个逻辑表达式的值就一定为 0,故无需再计算 b 部分的值;同理,在形如a∣b 的逻辑表达式中,会先计算 a 部分的值,如果a=1,那么整个逻辑表达式的值就一定为 1,无需再计算 b 部分的值。

输入格式

输入共一行,一个非空字符串 s 表示待计算的逻辑表达式。

输出格式

输出共两行,第一行输出一个字符 0 或 1,表示这个逻辑表达式的值;第二行输出两个非负整数,分别表示计算上述逻辑表达式的过程中,形如 a&b 和 a∣b 的"短路"各出现了多少次。

数据范围

设 ∣s∣ 为字符串 s 的长度。

对于所有数据,1≤∣s∣≤10^6。保证 s 中仅含有字符 01&|() 且是一个符合规范的逻辑表达式。保证输入字符串的开头、中间和结尾均无额外的空格。保证 s 中没有重复的括号嵌套(即没有形如 ((a)) 形式的子串,其中 a 是符合规范的逻辑表达式)。

测试点编号 s≤ 特殊性质
1∼2 3
3∼4 5
5 2000 1
6 2000 2
7 2000 3
8∼10 2000
11∼12 10^6 1
13∼14 10^6 2
15∼16 10^6 3
18∼20 10^6

其中:

特殊性质 1 为:保证 s 中没有字符 &。

特殊性质 2 为:保证 s 中没有字符 ∣。

特殊性质 3为:保证 s 中没有字符 ( 和 )。

提示

以下给出一个"符合规范的逻辑表达式"的形式化定义:

  • 字符串 0 和 1 是符合规范的;
  • 如果字符串 s 是符合规范的,且 s 不是形如 (t) 的字符串(其中t是符合规范的),那么字符串 (s) 也是符合规范的;
  • 如果字符串 a 和 b 均是符合规范的,那么字符串 a&b、a|b 均是符合规范的;
  • 所有符合规范的逻辑表达式均可由以上方法生成。

输出时每行末尾的多余空格,不影响答案正确性

样例输入 1

scss 复制代码
 0&(1|0)|(1|1|1&0)

样例输出 1

 1
 1 2

样例解释 1

该逻辑表达式的计算过程如下,每一行的注释表示上一行计算的过程:

scss 复制代码
  0&(1|0)|(1|1|1&0)
 =(0&(1|0))|((1|1)|(1&0)) //用括号标明计算顺序
 =0|((1|1)|(1&0))         //先计算最左侧的&,是一次形如a&b的"短路"
 =0|(1|(1&0))             //再计算中间的|,是一次形如a|b的"短路"
 =0|1                     //再计算中间的|,是一次形如a|b的"短路"
 =1

样例输入 2

scss 复制代码
 (0|1&0|1|1|(1|1))&(0&1&(1|0)|0|1|0)&0

样例输出 2

 0
 2 3
C++ 复制代码
     #include <bits/stdc++.h>
     using namespace std;
     stack<char> op;
     string s;
     struct Node{//本身的值v, yu->与短路次数, huo->或短路次数
         int yu = 0,huo = 0;
         int v;
     };
     stack<Node> num;// 此题的运算数 是一个 Node结构体,
     int opLevel(char ch){//定义各个运算符的优先级
         if(ch == '(') return 0;
         if(ch == '&') return 2;
         if(ch == '|') return 1;
     }
     void opOnce(int i){//清栈运算一次
         Node y = num.top();
         num.pop();
         Node x = num.top();
         num.pop();
         Node temp={0,0,0};
         char oo =  op.top();
         op.pop();
         if(oo == '&'){
             if(x.v == 1){
                 temp.yu = x.yu + y.yu;
                 temp.huo = x.huo + y.huo;
             }else if(x.v == 0){
                 temp.yu = x.yu + 1;
                 temp.huo = x.huo;
             }
             temp.v = x.v & y.v;
         }else if(oo == '|'){
             if(x.v == 1){
                 temp.yu = x.yu;
                 temp.huo = x.huo + 1;
             }else if(x.v == 0){
                 temp.yu = x.yu + y.yu ;
                 temp.huo = x.huo + y.huo;
             }
             temp.v = x.v | y.v;
         }
         num.push(temp);
     }
     void solve(){//表达式整体框架逻辑
         for(int i = 0; i < s.size();i ++){
             char ch = s[i];
             if(ch == '0' || ch == '1'){
                 num.push({0,0,ch-'0'});
             }else if(ch == '('){
                 op.push('(');
             }else if(ch == ')'){
                 while(op.top() != '('){
                     opOnce();
                 }
                 op.pop();
             }else{
                 while(!op.empty() && opLevel(op.top()) >= opLevel(ch)){
                     opOnce();
                 }
                 op.push(ch);
             }
         }
         while(!op.empty()){
             opOnce();
         }
         auto tp = num.top();
         cout << tp.v << endl;
         cout << tp.yu << ' ' <<tp.huo;
     }
     int main(){
         
         cin >> s;
         solve();
         return 0;
     }

总结

可以看到三个例子, 在求表达式的值的时候, 题意会有稍许的改变, 但是整体的框架是不变的. 变的是运算数的内容, 清栈一次的规则.以及各个题目中对于运算符的优先级的定义.

这样所有关于表达式的值的问题都能在这个框架上解决.而且各个题目的清栈方式,优先级的定义都封装成函数.使得结构更加清晰明了.

相关推荐
灰勒塔德25 分钟前
Linux-----进程处理(文件IO资源使用)
linux·运维·算法
xiaoshiguang329 分钟前
LeetCode:404.左叶子之和
java·算法·leetcode
计科土狗34 分钟前
前缀和与差分
c++·算法
前端切圖仔1 小时前
失业,仲裁,都赶上了(二)
前端·javascript·程序员
凭君语未可1 小时前
详解归并排序
算法·排序算法
simple_ssn2 小时前
【蓝桥杯】走迷宫
java·算法
simple_ssn2 小时前
【蓝桥杯】奇怪的捐赠
java·算法
从以前3 小时前
【算法题解】Bindian 山丘信号问题(E. Bindian Signaling)
开发语言·python·算法
不白兰3 小时前
[代码随想录23回溯]回溯的组合问题+分割子串
算法
御风@户外4 小时前
质数生成函数、质数判断备份
算法·acm