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

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

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

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

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

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

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

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

  1. #include<iostream>
  2. #include<vector>
  3. #include<cstdio>
  4. #include<map>
  5. #include<cstring>
  6. #include<string>
  7. using namespace std;
  8.  
  9. typedef map<string, double> MSD;
  10. //表达式类
  11. class Expression
  12. {
  13. public:
  14. virtual double Evaluate(MSD vars){ return 0; }
  15. };
  16. //常数类
  17. class Constant :public Expression
  18. {
  19. public:
  20. double value;
  21. Constant(double value)
  22. {
  23. this->value = value;
  24. }
  25. double Evaluate(MSD vars)
  26. {
  27. return value;
  28. }
  29. };
  30. //未知数类
  31. class VariableReference :public Expression
  32. {
  33. public:
  34. string name;
  35. VariableReference(string name)
  36. {
  37. this->name = name;
  38. }
  39. double Evaluate(MSD vars)
  40. {
  41. double value = vars[name];
  42. return value;
  43. }
  44. };
  45. //运算类
  46. class Operation :public Expression
  47. {
  48. public:
  49. //左边的表达式
  50. Expression *left;
  51. //当前运算
  52. char op;
  53. //右边的表达式
  54. Expression *right;
  55. Operation(Expression *left, char op, Expression *right)
  56. {
  57. this->left = left;
  58. this->op = op;
  59. this->right = right;
  60. }
  61. //计算值
  62. double Evaluate(MSD vars)
  63. {
  64. //递归计算
  65. double x = left->Evaluate(vars);
  66. double y = right->Evaluate(vars);
  67. //运算
  68. switch (op)
  69. {
  70. case '+':return x + y;
  71. case '-':return x - y;
  72. case '*':return x*y;
  73. case '/':return x / y;
  74. }
  75. }
  76. };
  77. //将字符串转换为树
  78. //s起始位置,t结束位置
  79. Expression *strToTree(string str, int s, int t)
  80. {
  81. //去除包含整个当前表达式的括号
  82. while (s <= t&&str[s] == '('&&str[t] == ')')s++, t--;
  83. if (s > t)
  84. return new Constant(0);
  85. //findLetter找到字母,用以判断是否为未知数
  86. //findChar找到字符,用以判断是否存在运算符
  87. bool findLetter = false, findChar = false;
  88. //括号标记
  89. int brackets = 0;
  90. //lastPS最后的加减法
  91. //lastMD最后的乘除
  92. int lastPS = -1, lastMD = -1;
  93.  
  94. for (int i = s; i <= t; i++)
  95. {
  96. //当前位置不是常数
  97. if (str[i] != '.' && (str[i]<'0' || str[i]>'9'))
  98. {
  99. //如果是字母的话
  100. if ((str[i] >= 'a'&&str[i] <= 'z') || (str[i] >= 'A'&&str[i] <= 'Z'))
  101. findLetter = true;
  102. else
  103. {
  104. //不是常数,不是字母,就是运算符
  105. findChar = true;
  106. switch (str[i])
  107. {
  108. case '(':brackets++; break;
  109. case ')':brackets--; break;
  110. //更新最后加减法的位置
  111. case '+':
  112. case '-':if (!brackets)lastPS = i; break;
  113. //更新最后乘除法的位置
  114. case '*':
  115. case '/':if (!brackets)lastMD = i; break;
  116. }
  117. }
  118. }
  119. }
  120. //从s到t都是常数
  121. if (findLetter == false && findChar == false)
  122. return new Constant(stod(str.substr(s, t - s + 1)));
  123. //从s到t是未知数
  124. if (findChar == false)
  125. return new VariableReference(str.substr(s, t - s + 1));
  126. //从s到t是个运算
  127. //没有加减就用乘除
  128. if (lastPS == -1)
  129. lastPS = lastMD;
  130. return new Operation(strToTree(str, s, lastPS - 1), str[lastPS], strToTree(str, lastPS + 1, t));
  131. }
  132.  
  133. int main()
  134. {
  135. MSD vars;
  136. //这里设置未知数
  137. vars["x"] = 123;
  138. vars["y"] = 100;
  139. //输入字符串
  140. string str;
  141. cin >> str;
  142. //转换为树
  143. Expression *exp = strToTree(str, 0, str.length() - 1);
  144. //输出值
  145. cout << exp->Evaluate(vars) << endl;
  146. return 0;
  147. }

这里我设置了两个未知数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. gdb插件使用方法

    0x00 peda peda 安装: git clone https://github.com/longld/peda.git ~/peda echo "source ~/peda/peda ...

  2. ios 序列化

    1到底这个序列化有啥作用? 面向对象的程序在运行的时候会创建一个复杂的对象图,经常要以二进制的方法序列化这个对象图,这个过程叫做Archiving. 二进制流可以通过网络或写入文件中(来源于某教材的一 ...

  3. javascript(函数式编程思考) ---> Map-Filter-quicksort-Collatz序列-Flodl-Flodr

    let add = x=>x+1; //Map :: ( a -> b) -> [a] -> [b] let Map = function(f,arr){ //闭包存储累积对象 ...

  4. ES6变量解构赋值的用法

    一.数组赋值(从数组中提取值,按照对应位置,对变量赋值) 1. 完全解构(变量与值数目相等) let arr = [1, 2, 3]; let [a,b,c] = arr; console.log(a ...

  5. 使用Redis作为高速缓存

    Redis适合哪些业务场景常规业务系统的数据库访问中,读写操作的比例一般在7/3到9/1,也就是说读操作远多于写操作,因此高并发系统设计里,通过NoSQL技术将热点数据(短期内变动概率小的数据)放入内 ...

  6. 【转发】【linux】【php】centos 编译php常见错误

    configure: error: xml2-config not found. Please check your libxml2 installation. yum install libxml2 ...

  7. mysql函数总结

    MySQL函数 MySQL数据库提供了很多函数包括: 数学函数:字符串函数:日期和时间函数:条件判断函数:系统信息函数:加密函数:格式化函数: 一.数学函数 数学函数主要用于处理数字,包括整型.浮点数 ...

  8. SVN 如何提交 SO 库文件

    今天提交代码时候发现,svn add 还是 svn st 均查看不到想要提交的 so 文件. 后来才知道原来是配置文件出了问题,把so文件的提交给屏蔽掉了. 修改步骤如下: 1.Ubuntu 系统,点 ...

  9. django第11天(分页器)

    django第11天分页器 分页模块 批量插入数据 book_list = [] #先生成对象 for i in range(100): book = Book(name = 'book%s'%i,p ...

  10. Python9-迭代器-生成器-day13

    迭代器# print('__iter__' in dir(int))# print('__iter__' in dir(list))# print('__iter__' in dir(dict))# ...