一些常见表达式的处理

前言

笨人在学习程序设计实习这门课的时候,遇到了一些编程模拟表达式求值的问题,问题虽然不是很难,但是不同的表达式有不同的处理技巧和坑点,并且每一种表达式都有自己的优势,故现写一篇总结的文章,强化自己的记忆。

一般表达式

描述

一般表达式顾名思义就是我们人类平时写的表达式,又称作中缀表达式,比如2+3, (2+8)*6……事实上,这又称为中缀表达式,要求操作符(运算符号)要位于操作数(数字)的中间。这样的式子易于我们人类阅读和计算。下马通过一个简单的例子来说明如何模拟这一运算过程。

题目

输入为四则运算表达式,仅由数字、+、-、*、/ 、(、) 组成,没有空格,要求求其值。假设运算符结果都是整数。”/” 结果也是整数.

分析
试回忆一下我们平时是怎么计算一个简单的表达式的呢?
首先,四则运算有优先级之分,* 和 / 的优先级高于 + 和 - ,此外,()的优先级最高。
那么,比如我们遇见表达式2*(2+5),按从左到右的顺序来阅读,首先看到数字2, 然后是* 所以我们知道这个*的作用应该是把2和另外一个因子的相乘,于是我们很自然的往后看,寻找那个因子。在这个例子中,后面是( ,所以我们知道了此时应该要先算一个的值作为前面提到的因子,参与之前*的运算。

在上面对于特定例子的讨论中,我们在表达式的基础上抽象出了因子的概念。事实上,我们不难发现,一个表达式可以看作是一些通过 + 和 - 运算连接起来,而又是由一些因子通过 * 和 / 运算连接起来,特别地,一个表达式在括号的作用下又可以构成一个因子参与运算。

经过上面的分析,我们发现一个一般表达式实际上是在递归地进行着某一些操作,于是我们很自然地想到可以用递归算法来模拟这一求值的过程。

代码
根据分析,可以写出以下代码,注意程序中对*cin.peek()*函数的使用(从输入流中读入一个字符,不取出这个字符,字符仍留在缓冲区中)。

#include<bits/stdc++.h>
using namespace std;

int expression_value();
int term_value();
int factor_value();

int main(){
    cout<<expression_value()<<endl;
    return 0;
}

int expression_value(){
    int result=term_value();
    bool more=true;//用来判断该部分的输入是否结束
    while(more){
        char op=cin.peek();
        if (op=='+'||op=='-'){
            cin.get();
            int value=term_value();
            if (op=='+') result+=value;
            else result-=value;
        }
        else {
            more=false;
        }
    }
    return result;
}

int term_value(){
    int result=factor_value();
    bool more=true;
    while(more){
        char op=cin.peek();
        if (op=='*'||op=='/'||op=='^'){
            cin.get();
            int value=factor_value();
            if (op=='*'){
                result*=value;
            }
            else if (op=='^'){//这里可以认为乘方的优先级和*和/一样,因为在输入中^是一个二元运算符
                result=pow(result,value);
            }
            else result/=value;
        }
        else more=false;
    }
    return result;
}

int factor_value(){
    int result=0;
    char op=cin.peek();
    //这里不再需要more来判断是否结束,因为只需要获取一个值就可以返回
    if (op=='('){
        cin.get();
        result=expression_value();
        cin.get();
    }
    else cin>>result;
    return result;
}

布尔表达式

描述

布尔表达式是一般表达式的逻辑运算版本,也是由一些运算符号按照我们人类的阅读与理解方式连接一些值组成的。具体来说,布尔表达式的值只有真或假两种,运算符号主要包括与或非。布尔表达式在计算机中非常有用,基于布尔表达式的SAT问题具有很高的研究价值。
这是WIKIPEDIA对于布尔表达式的定义:
In computer science, a Boolean expression is an expression used in programming languages that produces a Boolean value when evaluated. A Boolean value is either true or false. A Boolean expression may be composed of a combination of the Boolean constants True/Yes or False/No, Boolean-typed variables, Boolean-valued operators, and Boolean-valued functions.详情请见
下面通过一个例题来说明如何编程处理这种类型的表达式。

题目

The objective of the program you are going to produce is to evaluate boolean expressions as the one shown next:
Expression: ( V | V ) & F & ( F | V )
where V is for True, and F is for False. The expressions may include the following operators: ! for not , & for and, | for or , the use of parenthesis for operations grouping is also allowed.
To perform the evaluation of an expression, it will be considered the priority of the operators, the not having the highest, and the or the lowest. The program must yield V or F , as the result for each expression in the input file.
输入
The expressions are of a variable length, although will never exceed 100 symbols. Symbols may be separated by any number of spaces or no spaces at all, therefore, the total length of an expression, as a number of characters, is unknown.
The number of expressions in the input file is variable and will never be greater than 20. Each expression is presented in a new line, as shown below.
输出
For each test expression, print "Expression " followed by its sequence number, ": ", and the resulting value of the corresponding test expression. Separate the output for consecutive test expressions with a new line.
Use the same format as that shown in the sample output shown below.
样例
input:
( V | V ) & F & ( F| V)
!V | V & V & !F & (F | V ) & (!F | F | !V & V)
(F&F|V|!V&!F&!(F|F&V))
output:
Expression 1: F
Expression 2: V
Expression 3: V

分析
按照题目中的描述,运算符按优先级排序为!> () > & > | ,于是我们可以借用一般表达式中的思想,按照expression_value(), term_value(), factor_value()的框架递归求解。
| 优先级最低,类比为 + 和 - ;& 优先级高于 | ,类比为 * 和 / ;!优先级最高,只要在处理因子时特判就可以,大框架与一般表达式相比不发生改变。
代码

/*Boolean Expressions*/
#include<bits/stdc++.h>
using namespace std;

bool factor_value();
bool expression_value();
bool term_value();

int main(){
    int i=1;
    while(cin.peek()!=EOF){
        cout<<"Expression "<<i++<<": ";
        if (expression_value()) cout<<"V\n";
        else cout<<"F\n";
        cin.get();
    }
    return 0;
}

bool expression_value(){
    bool result=term_value();
    bool more=true;
    while(more){
        char ch=cin.peek();
        if (ch==' '){
            cin.get();
        }
        else if (ch=='|'){
        //|的优先级最低,所以项之间使用|连接从而构成表达式
            cin.get();
            bool value=term_value();
            result=(result||value);
        }
        else more=false;
    }
    return result;
}
bool term_value(){
    bool result=factor_value();
    bool more=true;
    while(more){
        char ch=cin.peek();
        if (ch==' '){
            cin.get();
        }
        else if (ch=='&'){
        //&的优先级次低,用来连接因子
            cin.get();
            bool value=factor_value();
            result=result&&value;
        }
        else more=false;
    }
    return result;
}
bool factor_value(){
    bool result;
    char ch=cin.peek();
    while (ch==' '){
        cin.get();
        ch=cin.peek();
    }
    if (ch=='('){
        cin.get();
        result=expression_value();
        cin.get();
    }
    else if (ch=='!'){
    //!优先级最高
        cin.get();
        result=!(factor_value());
    }
    else if (ch=='V'){
        cin.get();
        result=true;
    }
    else if (ch=='F'){
        cin.get();
        result=false;
    }
    return result;
}

波兰表达式and逆波兰表达式

描述
还是先看看维基百科对于两者的定义

Polish notation (PN), also known as normal Polish notation (NPN),[1] Łukasiewicz notation, Warsaw notation, Polish prefix notation or simply prefix notation, is a mathematical notation in which operators precede their operands, in contrast to the more common infix notation, in which operators are placed between operands, as well as reverse Polish notation (RPN), in which operators follow their operands. It does not need any parentheses as long as each operator has a fixed number of operands. The description “Polish” refers to the nationality of logician Jan Łukasiewicz,[2]: 24 [3]: 78 [4] who invented Polish notation in 1924.详情请见

简单来说二者就是在一般表达式的基础上对运算符和数字的顺序进行改变,波兰表达式又称为前缀表达式,把运算符写在操作数之前;逆波兰表达式又称为后缀表达式,把运算符写在操作数之后。这两种表示方式看起来着实“反直觉”,但是在计算机看来他们相对于一般表达式而言反而要更“顺眼”,他们的定义决定了他们不需要()来决定运算顺序,而且能更好的适应于计算机系统。下面通过例题看看二者具体怎么实现。

波兰表达式

题目

波兰表达式
波兰表达式是一种把运算符前置的算术表达式,例如普通的表达式 2 +3 的波兰表示法为 + 2 3。波兰表达式的优点是运算符之间不必有优先级关系,也不必用括号改变运算次序,例如 (2 + 3) * 4 的波兰表示法为2 3 4。本题求解波兰表达式的值,其中运算符包括 + - * /四个。波兰表达式的定义:
1 一个数是一个波兰表达式,值为该数
2 ” 运算符 波兰表达式 波兰表达式” 是波兰表达式,值为两个波兰表达式的值运算的结果
样例输入
*+11.0 12.0 + 24.0 35.0
样例输出
1357.000000
提示
(11.0+12.0)*(24.0+35.0)

分析

由于操作符总是位于两个波兰表达式之前,我们仍然可以用递归来计算整体的值。具体地,每次读入的如果是运算符,就递归地读入两个操作数进行该运算符对应的运算,否则直接输入数字。

代码

/*波兰表达式*/
#include<bits/stdc++.h>
using namespace std;

double expression(){
    double a=0,b=0,result=0;
    char op;
    op=cin.peek();
    if (op==' '){
        cin.get();
        op=cin.peek();
    }
    if (op=='*'){
        cin.get();
        a=expression();
        b=expression();
        result=a*b;
    }
    else if (op=='+'){
        cin.get();
        a=expression();
        b=expression();
        result=a+b;
    }
    else if (op=='-'){
        cin.get();
        a=expression();
        b=expression();
        result=a-b;
    }
    else if (op=='/'){
        cin.get();
        a=expression();
        b=expression();
        result=a/b;
    }
    else {
        cin>>result;
    }
    return result;
}


int main(){
    cout<<setprecision(6)<<fixed<<expression()<<endl;
    return 0;
}

逆波兰表达式

题目

Reverse Polish notation (or just RPN) by analogy with the related Polish notation, a prefix notation introduced in 1920 by the Polish mathematician Jan Łukasiewicz, is a mathematical notation wherein every operator follows all of its operands. It is also known as Postfix notation.
In Reverse Polish notation the operators follow their operands; for instance, to add three and four one would write “3 4 +” rather than “3 + 4”. If there are multiple operations, the operator is given immediately after its second operand; so the expression written “3 − 4 + 5” in conventional infix notation would be written “3 4 − 5 +” in RPN: first subtract 4 from 3, then add 5 to that. An advantage of RPN is that it obviates the need for parentheses that are required by infix. While “3 − 4 * 5” can also be written “3 − (4 * 5)”, that means something quite different from “(3 − 4) * 5”, and only the parentheses disambiguate the two meanings. In postfix, the former would be written “3 4 5 * −”, which unambiguously means "3 (4 5 ) −".
You were asked to design a simple RPN calculator, which will support the “+”, “-“, “
”, “/”(the absolute value of the divisor will not less then 10^-9) and “^”(power operator, if the base number b<=0, the exponential e must be a positive integer not greater than 10^9) operators. You can assume all the numbers during the calculation can fit into a double-precision floating point number.
In addition, our calculator has some memory. Each time we calculate an expression, the smallest number in the memory will be erased, and replace it with the value of the expression.
Input:
The first line contains an integer n, which is the memory size of our calculator.
From the second line, we will give n numbers, which is the initial value of the memory. each line except last will have 10 numbers.
And then each line has a valid RPN expression we previously described, end with “=”, which is the command for calculation. Each term will no longer than 20 characters.
Output
For each expression, output the value of it in a line.
And then output an empty line to separate the two parts.
At last, output the all the numbers in memory, in increasing order, 10 numbers per line.
Each number should be formatted in scientific notation with 6 digits after decimal point and 2 digits of exponential, such like “%e” format string of printf() function in C. The numbers in a line should be separated by a space.

样例:
4
1e6 1e-6 0.001 1000
1 2 + 3 4 + * =
1 0.1 / 8 ^ =

2.100000e+01
1.000000e+08

2.100000e+01 1.000000e+03 1.000000e+06 1.000000e+08

分析
本题除了实现计算逆波兰表达式之外,还要求维护一个内存序列,只用加一个优先队列即可。那么核心在于如何计算一个这样形式的表达式。与上面几种表达式不同,由于操作符放在值的后边,你在读入两个值后无法立即获知应该对这两个值进行哪种运算,于是必须先把读入的数字储存,在用到的时候拿出与运算符最靠近的那两个数字,也即最后放入储存区的那两个数字。要实现这个过程递归显然不如栈方便,可以轻松保证每次取出的都是最后放入的元素,这就完全符合我们的预期。

#include<bits/stdc++.h>
using namespace std;
int n;
priority_queue<double,vector<double>,greater<double>>memories;

void cal(string str,stack<double>&s){
    double a=s.top();
    s.pop();
    double b=s.top();
    s.pop();
    if (str=="+") s.push(a+b);
    else if (str=="-") s.push(b-a);
    else if (str=="*") s.push(a*b);
    else if (str=="/") s.push(b/a);
    else if (str=="^") s.push(pow(b,a));
    return ;
}
void RPN(string &str,stack<double>&s){
    if (str!="+"&&str!="-"&&str!="*"&&str!="/"&&str!="^"){
        s.push(atof(str.c_str()));
    }
    else cal(str,s);
    return ;
}
int main(){
    scanf("%d",&n);
    for (int i=0;i<n;++i){
        double x;
        scanf("%lf",&x);
        memories.push(x);
    }
    string cmd;
    stack<double>s;
    while(getline(cin,cmd)){
        int i=0,j=cmd.size()-1;
        for (int k=0;k<=cmd.size();k++){
            if (cmd[k]==' '||cmd[k]=='\0'){
            //因为缓冲区中的浮点数也是以string的形式输入的,所以需要手动截取出来每一次参与运算的字符
                j=k;
                string str=cmd.substr(i,j-i);
                i=k+1;
                if (str=="="){
                    printf("%e\n",s.top());
                    memories.pop();
                    memories.push(s.top());
                    s.pop();
                }
                else RPN(str,s);
            }
        }
    }
    int cnt=0;
    printf("\n");
    for (int i=1;i<=n;i++){
    //如此输出只是为了满足题意,没有什么特别的:)
        cnt++;
        if (i==n){
            printf("%e",memories.top());
        }
        else if (cnt%10==0){
            printf("%e\n",memories.top());
        }
        else printf("%e ",memories.top());
        memories.pop();
    }
    return 0;
}

注:

这篇文章是笨人学习时的一些小总结,例题选自北京大学程序设计实习课程张勤健老师的课间,以及POJ上的一些作业题,代码部分为笨人自己摸索,如有不对还望大佬斧正Orz

  • 30
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值