首先允许我吐槽CSDN的MARKDOWN,简直难用的不行。

程序的原理是将表达式分治转换为二叉树,再在二叉树上递归计算结果。如同以下表达式:x+5*y-(6/(1-5.5))可以表达为以下二叉树(抱歉,本来想弄Visualgo的,结果上不了,只能用word来做画面了):

为什么是这样的二叉树呢?仔细想想平时是怎么计算这个表达式的,毫无疑问的是最后一步是减法,倒数的第二步是加法和除法……所以我们不难得出,将最后一步作为整个树的根,然后将这次运算的左边的表达式(可能是运算、未知数、常数)和右边的表达式进行递归建树,就能建立这样的二叉树了。

那为什么要建立这个二叉树呢?如果这样的二叉树建立好了,就很容易递归(从下至上)得到答案了:

那么现在的问题就在于如何将表达式字符串转换为表达式树,从先前的说明中,我们不难发现,关键就在于寻找当前表达式中的最后一步,作为子树的根。为了达到这个目的,我们首先去除掉包含整个表达式的括号,这样一来,最后一步计算一定不在括号中,这里可以用一个变量brackets来标记,初始化brackets=0,每当遇到左括号就++,右括号就--,所以,当且仅当brackets==0时,当前的位置是处在括号外的。在扫的过程中,不断的更新在括号外的最后加减法和最后乘除法的位置。最后得到的加减法的位置(如果没有加减法,就用乘除)就是当前表达式的最后运算。

未知数的处理是靠map实现的。

整个程序由基类表达式类和三个继承自表达式类的类组成,其中Evaluate函数(返回计算数据)由Expression类定义,由三个派生类重写。函数strToTree是将字符串转换为表达式二叉树:

详细见代码,注释写的非常详细:

#include<iostream>
#include<vector>
#include<cstdio>
#include<map>
#include<cstring>
#include<string>
using namespace std; typedef map<string, double> MSD;
//表达式类
class Expression
{
public:
virtual double Evaluate(MSD vars){ return 0; }
};
//常数类
class Constant :public Expression
{
public:
double value;
Constant(double value)
{
this->value = value;
}
double Evaluate(MSD vars)
{
return value;
}
};
//未知数类
class VariableReference :public Expression
{
public:
string name;
VariableReference(string name)
{
this->name = name;
}
double Evaluate(MSD vars)
{
double value = vars[name];
return value;
}
};
//运算类
class Operation :public Expression
{
public:
//左边的表达式
Expression *left;
//当前运算
char op;
//右边的表达式
Expression *right;
Operation(Expression *left, char op, Expression *right)
{
this->left = left;
this->op = op;
this->right = right;
}
//计算值
double Evaluate(MSD vars)
{
//递归计算
double x = left->Evaluate(vars);
double y = right->Evaluate(vars);
//运算
switch (op)
{
case '+':return x + y;
case '-':return x - y;
case '*':return x*y;
case '/':return x / y;
}
}
};
//将字符串转换为树
//s起始位置,t结束位置
Expression *strToTree(string str, int s, int t)
{
//去除包含整个当前表达式的括号
while (s <= t&&str[s] == '('&&str[t] == ')')s++, t--;
if (s > t)
return new Constant(0);
//findLetter找到字母,用以判断是否为未知数
//findChar找到字符,用以判断是否存在运算符
bool findLetter = false, findChar = false;
//括号标记
int brackets = 0;
//lastPS最后的加减法
//lastMD最后的乘除
int lastPS = -1, lastMD = -1; for (int i = s; i <= t; i++)
{
//当前位置不是常数
if (str[i] != '.' && (str[i]<'0' || str[i]>'9'))
{
//如果是字母的话
if ((str[i] >= 'a'&&str[i] <= 'z') || (str[i] >= 'A'&&str[i] <= 'Z'))
findLetter = true;
else
{
//不是常数,不是字母,就是运算符
findChar = true;
switch (str[i])
{
case '(':brackets++; break;
case ')':brackets--; break;
//更新最后加减法的位置
case '+':
case '-':if (!brackets)lastPS = i; break;
//更新最后乘除法的位置
case '*':
case '/':if (!brackets)lastMD = i; break;
}
}
}
}
//从s到t都是常数
if (findLetter == false && findChar == false)
return new Constant(stod(str.substr(s, t - s + 1)));
//从s到t是未知数
if (findChar == false)
return new VariableReference(str.substr(s, t - s + 1));
//从s到t是个运算
//没有加减就用乘除
if (lastPS == -1)
lastPS = lastMD;
return new Operation(strToTree(str, s, lastPS - 1), str[lastPS], strToTree(str, lastPS + 1, t));
} int main()
{
MSD vars;
//这里设置未知数
vars["x"] = 123;
vars["y"] = 100;
//输入字符串
string str;
cin >> str;
//转换为树
Expression *exp = strToTree(str, 0, str.length() - 1);
//输出值
cout << exp->Evaluate(vars) << endl;
return 0;
}

这里我设置了两个未知数x,y。运行结果如下图所示:

四则运算表达式树 C++模板 支持括号和未知数的更多相关文章

  1. 表达式树(Expression Trees)

    [翻译]表达式树(Expression Trees) 原文地址:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/con ...

  2. 表达式树(Expression Tree)

    饮水思源 本文并非原创而是下面网址的一个学习笔记 https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/e ...

  3. C#3.0新增功能10 表达式树 02 说明

    连载目录    [已更新最新开发文章,点击查看详细] 表达式树是定义代码的数据结构. 它们基于编译器用于分析代码和生成已编译输出的相同结构.表达式树和 Roslyn API 中用于生成分析器和 Cod ...

  4. 【C#表达式树 开篇】 Expression Tree - 动态语言

    .NET 3.5中新增的表达式树(Expression Tree)特性,第一次在.NET平台中引入了"逻辑即数据"的概念.也就是说,我们可以在代码里使用高级语言的形式编写一段逻辑, ...

  5. C#3.0新增功能10 表达式树 03 支持表达式树的框架类型

    连载目录    [已更新最新开发文章,点击查看详细] 存在可与表达式树配合使用的 .NET Core framework 中的类的大型列表. 可以在 System.Linq.Expressions 查 ...

  6. C# Lambda表达式详解,及Lambda表达式树的创建

    最近由于项目需要,刚刚学完了Action委托和Func<T>委托,发现学完了委托就必须学习lambda表达式,委托和Lambda表达式联合起来,才能充分的体现委托的便利.才能使代码更加简介 ...

  7. C#3.0新特性:隐式类型、扩展方法、自动实现属性,对象/集合初始值设定、匿名类型、Lambda,Linq,表达式树、可选参数与命名参数

    一.隐式类型var 从 Visual C# 3.0 开始,在方法范围中声明的变量可以具有隐式类型var.隐式类型可以替代任何类型,编译器自动推断类型. 1.var类型的局部变量必须赋予初始值,包括匿名 ...

  8. jQuery find() 搜索所有段落中的后代 C# find() 第一个匹配元素 Func 有返回值 Action是没有返回值 Predicate 只有一个参数且返回值为bool 表达式树Expression

    所有p后代span Id为 TotalProject 的 select 标签 的后代 option标签 为选中的 text using System; using System.Collections ...

  9. 【C++】朝花夕拾——表达式树

    表达式树: 叶子是操作数,其余结点为操作符,是二叉树的其中一种应用 ====================我是分割线====================== 一棵表达式树如下图: 若是对它做中序 ...

随机推荐

  1. PDO drivers no value 解决办法

    我的服务器是windos系统的,而且我也已经开启了PDO扩展,但是查看phpinfo的时候,结果却如下图: 解决办法 修改 php.ini 中的 extension_dir 路径即可! 将extens ...

  2. 51nod 1242 斐波那契数列的第N项——数学、矩阵快速幂

    普通算法肯定T了,所以怎么算呢?和矩阵有啥关系呢? 打数学符号太费时,就手写了: 所以求Fib(n)就是求矩阵  |  1  1  |n-1  第一行第一列的元素. |  1  0  | 其实学过线代 ...

  3. (63)zabbix low-level discover zabbix批量部署必备

    1. 概述 <zabbix发现配置>server通过配置好的规则,自动添加host.group.template <zabbix Active agent自动注册>与disco ...

  4. linux c中需要记住的东西

    1.记住常见的字符的ASCII值 a------------97     b------------98     A------------65     B------------66     空格' ...

  5. 关于Powershell执行时的问题

    问题1: [问题描述] 使用Invoke-Command命令登录远程主机执行命令时,提示如下错误: [192.168.1.135] 连接到远程服务器失败,错误消息如下: WinRM 客户端无法处理该请 ...

  6. TMG 模拟公司网络架构要点

    1.部署的router 有且只有一个网关,指向TMG服务器 2.router 确认有默认路由,指向TMG服务器 3.TMG 只能设置一个网关,指向模拟公网关 4.TMG 要手工创建指向router的路 ...

  7. 为什么我打的jar包没有注解?

    本文来自网易云社区 作者:王飞 一.前言 一切问题的起源就是来自一个问题"为什么我打的jar包没有注解?",带着这个疑问查了一圈资料,原来问题主要是在没有将源码中的注释进行抽取打包 ...

  8. debian 9 安装AMD驱动

    目录 debian 9 安装AMD驱动 安装驱动之前: 安装驱动: 安装驱动之后: debian 9 安装AMD驱动 需求说明: 安装完成debian系统后独显驱动未安装 操作系统版本: kyeup@ ...

  9. WordPress添加前台注册功能

    一.添加注册表单 1.首先在当前主题的目录下新建一个php文件,命名为reg-page.php,然后将page.php中的所有代码复制到reg-page.php中: 2.删除reg-page.php开 ...

  10. EIGRP

    因为rip的收敛时间长  尤其是使用过程中   链路down掉    重收敛的时间比较长  所以在中到大型的园区网中很少用到rip协议 只有在很小的局域网中用到rip   因为收敛时间可能会稍微短一些 ...