相关介绍:

 该算法用于求得一个字符串形式的表达式的结果。例如,计算1+1+(3-1)*3-(21-20)/2所得的表达式的值,该算法利用了两个栈来计算表达式的值,为此,称为双栈法,其实现简单且易于理解。但其要求将我们平时所看到的表达式的模式转化为完全加括号的形式。如表达式,1+1+(3-1)*3-(21-20)/2是我们平时习惯上的样子,其完全加括号的形式为,(((1+1)+((3+1)*2))-((21-20)/2))。由此可知,计算一个字符串形式的表达式有两个任务,第一是将输入的算术表达式转化为一个完全加括号的算术表达式,第二是将一个完全加括号的算术表达式进行运算,求得运算的结果。为方便起见,这里只考虑加减乘除(+、-、×、÷)这四个基本运算

将算术表达式转化为完全加括号:

其基本思想如下:

  1. 初始化一个运算符栈和一个操作数栈
  2. 从算术表达式输入的字符串中从左到右的读取一个字符
  3. 若当前字符为操作数,则直接将该操作数压入操作数栈中
  4. 若当前字符是左括号"("时,将其压入运算符栈中
  5. 若当前字符为运算符时,则:
    1. 当运算符栈为空的时候,则将其压入运算符栈中
    2. 当此运算符的优先级高于栈顶元素的运算符的时候,则将此运算符压入操作数栈中,否则,弹出运算符栈顶元素和操作数栈顶的两个元素,为其添加上相应的运算符以及左括号和右括号之后,将其压入操作数栈中,将其看成一个整体
  6. 若当前字符为右括号")"时,反复将栈顶元素弹出,每次弹出一个运算符的时候,从操作数栈中弹出栈顶的两个元素为其添加上相应的运算符以及左右括号之后,再将其压入操作数栈中,将其看成一个整体。直至运算符栈的栈顶元素为左括号为止,再将左括号出栈并丢弃。
  7. 若读取还未完毕,重复步骤2
  8. 若读取完毕,则将栈中剩余的所有运算符依次弹出,每次弹出一个运算符时,同时弹出操作数栈的两个元素,并为其添加上相应的运算符以及左右括号之后,将其作为一个整体,压入操作数栈中。直到运算符栈为空为止,则操作数栈中的结果,即为所得的结果。

运算符的优先级如下表所示:

运算符 +(加)、-(减) *(乘)、/(除)
优先级 1 2

其中,其数值越大的表示其运算符的优先级越高。

示例代码如下:

  1. package queueandstack;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. import java.util.Stack;
  5. /**
  6. * 该类用于演示使用双栈法求解算术表达式的结果
  7. * @author 学徒
  8. *
  9. */
  10. public class DoubleStackGetResult
  11. {
  12. public static void main(String[] args)
  13. {
  14. DoubleStackGetResult ds=new DoubleStackGetResult();
  15. System.out.println(ds.ComplementBrackets("1+1+(3+1)*2-(21-20)/2"));
  16. }
  17. //该方法用于实现一个符号表
  18. private static Map<String,Integer> getMap()
  19. {
  20. Map<String,Integer> temp=new HashMap<String,Integer>();
  21. //定义各个运算符的优先级,其中,x和÷字符用于兼容
  22. temp.put("*", 2);
  23. temp.put("/", 2);
  24. temp.put("×",2);
  25. temp.put("÷",2);
  26. temp.put("+", 1);
  27. temp.put("-",1);
  28. return temp;
  29. }
  30. /**
  31. * 该方法用于将输入的,习惯上的算术表达式转化为完全加括号的形式
  32. * @param input 输入的习惯上的算术表达式
  33. */
  34. //该符号表用于定义运算符的优先级
  35. private Map<String,Integer> table=getMap();
  36. //该字符串为用于匹配数字的
  37. private String rexNumber="\\d+";
  38. private String rexOperator="[((+\\-*/×÷))]";
  39. //操作数栈
  40. Stack<String> number=new Stack<String>();
  41. //运算符栈
  42. Stack<String> save=new Stack<String>();
  43. /**
  44. * 该方法用于将中序表达式转化为后序表达式,并对其转化后的表达式以字符串的形式进行返回
  45. * @return 后序表达式
  46. */
  47. public String ComplementBrackets(String input)
  48. {
  49. //用于获得字符串中的数字所组成的数组
  50. String[] numbers=input.split(rexOperator);
  51. //用于指示是获取了第几个数字数组中的数字整体
  52. int order=0;
  53. //用于指示当前字符的指针
  54. int i=0;
  55. while(i<input.length())
  56. {
  57. //获得当前字符
  58. String thisString=String.valueOf(input.charAt(i));
  59. //当前该字符为运算符或者括号时,即当前该字符不为数字时
  60. if(thisString.matches(rexOperator))
  61. {
  62. //当当前字符不为左括号或者右括号时(即为运算符)
  63. if(!thisString.matches("[()()]"))
  64. {
  65. //用于记录栈顶元素的优先级
  66. int temporary=0;
  67. //获取当前字符的优先级
  68. int present=table.get(thisString);
  69. //当操作数的栈不为空的时候
  70. if(!save.isEmpty())
  71. {
  72. //查看栈顶元素的字符以及其优先级
  73. String top=save.peek();
  74. if(!top.matches("[((]"))
  75. {
  76. temporary=table.get(top);
  77. }
  78. }
  79. //当栈顶元素的操作符的优先级比当前操作符的优先级还要高或者相同时,对其进行弹出操作,直到栈顶元素的优先级比当前操作符的优先级要低
  80. if(temporary>=present)
  81. {
  82. while(!save.isEmpty()&&table.get(save.peek())>=present)
  83. {
  84. String number1=number.pop();
  85. String number2=number.pop();
  86. number.push("("+number2+save.pop()+number1+")");
  87. }
  88. }
  89. save.push(thisString);
  90. }
  91. //当当前的字符为左括号的时候,直接将其压入栈中
  92. else if(thisString.matches("[((]"))
  93. {
  94. save.push(thisString);
  95. }
  96. //当当前的字符为右括号的时候,将其栈中的元素一直弹出,直至遇到左括号结束,并将左括号弹出
  97. else
  98. {
  99. while(!save.peek().matches("[((]"))
  100. {
  101. String number1=number.pop();
  102. String number2=number.pop();
  103. number.push("("+number2+save.pop()+number1+")");
  104. }
  105. //弹出其左括号
  106. save.pop();
  107. /*String number1=number.pop();
  108. String number2=number.pop();
  109. number.push("("+number2+save.pop()+number1+")");*/
  110. }
  111. i++;
  112. }
  113. //当前该字符为数字的时候
  114. if(thisString.matches(rexNumber))
  115. {
  116. //用于存储数字数组中的数字字符串
  117. String numberString=null;
  118. do
  119. {
  120. numberString=numbers[order];
  121. //当数字字符串中的数字不为空时(由于可能会是空字符串的出现),将整个中序表达式的字符串的指针进行向右移动
  122. if(!numberString.trim().equals(""))
  123. {
  124. i+=numberString.length();
  125. order++;
  126. break;
  127. }
  128. else
  129. {
  130. order++;
  131. }
  132. }while(true);
  133. //将数字直接压入操作数栈中
  134. number.push(numberString);
  135. }
  136. }
  137. //将栈中剩余的字符进行弹出
  138. while(!save.isEmpty())
  139. {
  140. String number1=number.pop();
  141. String number2=number.pop();
  142. number.push("("+number2+save.pop()+number1+")");
  143. }
  144. return number.pop();
  145. }
  146. }
  147. 运行结果如下:
  148. (((1+1)+((3+1)*2))-((21-20)/2))

计算完全加括号表达式的结果:

其基本思想如下:

  1. 初始化两个栈,一个用于保存运算符,一个用于保存操作数
  2. 从左往右依次遍历字符串表达式的每个字符
  3. 将操作数压入操作数栈中
  4. 将运算符压入运算符栈中
  5. 忽略左括号
  6. 在遇到右括号时,弹出一个运算符以及所需数量的操作数,并将运算符和操作数的运行结果压入到操作数栈中
  7. 处理完最后一个右括号,操作数栈上只会有一个值,它就是表达式的值

这种方法不难理解,每当算法遇到一个被括号包围并由一个运算符和两个操作数组成的子表达式时,它都能够将运算符和操作数的计算结果压入操作数栈中,这样的结果就是在输入中用这个运算所得的值代替了该子表达式,因此,用这个值代替子表达式得到的结果和原表达式相同,通过反复运用以上的规律,最终可以得到该表达式的解。

其示例代码如下:

  1. /**
  2. *
  3. * 用于计算一个完全加括号的算术表达式的结果
  4. * @param inputStr 其参数为完全加括号的算术表达式
  5. *
  6. */
  7. public double getResult(String inputStr)
  8. {
  9. //用于将输入的字符串分割成数字的正则表达式
  10. String regex="[((+\\-*/×÷))]";
  11. //用于获取得到数字
  12. String[] numbers =inputStr.split(regex);
  13. int order=0;
  14. //两个栈,一个为操作数栈,一个为运算符栈
  15. Stack<String> ops=new Stack<String>();
  16. Stack<Double> vals=new Stack<Double>();
  17. char[] input=inputStr.toCharArray();
  18. //用于遍历的字符的指针
  19. int index=0;
  20. while(index<input.length)
  21. {
  22. //读取对应的字符
  23. char ch=input[index];
  24. //忽略左括号
  25. if(ch=='('||ch=='(');
  26. else if(ch=='+')
  27. ops.push(String.valueOf(ch));
  28. else if(ch=='-'||ch=='-')
  29. ops.push(String.valueOf(ch));
  30. else if(ch=='*'||ch=='×')
  31. ops.push(String.valueOf(ch));
  32. else if(ch=='/'||ch=='÷')
  33. ops.push(String.valueOf(ch));
  34. else if(ch==')'||ch==')')
  35. {
  36. //当为右括号的时候,弹出运算符以及操作数,计算结果并压入栈中
  37. String op=ops.pop();
  38. double v=vals.pop();
  39. if(op.equals("+"))
  40. v=vals.pop()+v;
  41. else if(op.equals("-")||op.equals("-"))
  42. v=vals.pop()-v;
  43. else if(op.equals("*")||op.equals("×"))
  44. v=vals.pop()*v;
  45. else if(op.equals("/")||op.equals("÷"))
  46. v=vals.pop()/v;
  47. vals.push(v);
  48. }
  49. else
  50. {
  51. //用于存储数字数组中的数字字符串
  52. String numberString=null;
  53. do
  54. {
  55. numberString=numbers[order];
  56. //当数字字符串中的数字不为空时(由于可能会是空字符串的出现),将整个中序表达式的字符串的指针进行向右移动
  57. if(!numberString.trim().equals(""))
  58. {
  59. index+=numberString.length()-1;
  60. order++;
  61. break;
  62. }
  63. else
  64. {
  65. order++;
  66. }
  67. }while(true);
  68. vals.push(Double.parseDouble(numberString));
  69. }
  70. ++index;
  71. }
  72. return vals.pop();
  73. }

回到目录|·(工)·)

K:双栈法求算术表达式的值的更多相关文章

  1. PTA笔记 堆栈模拟队列+求前缀表达式的值

    基础实验 3-2.5 堆栈模拟队列 (25 分) 设已知有两个堆栈S1和S2,请用这两个堆栈模拟出一个队列Q. 所谓用堆栈模拟队列,实际上就是通过调用堆栈的下列操作函数: int IsFull(Sta ...

  2. pat02-线性结构3. 求前缀表达式的值(25)

    02-线性结构3. 求前缀表达式的值(25) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 算术表达式有前缀表示法.中缀表示法和后缀表示法 ...

  3. 信息竞赛进阶指南--递归法求中缀表达式的值,O(n^2)(模板)

    // 递归法求中缀表达式的值,O(n^2) int calc(int l, int r) { // 寻找未被任何括号包含的最后一个加减号 for (int i = r, j = 0; i >= ...

  4. openjduge 求简单表达式的值

    表达式求值 总时间限制:  10000ms  单个测试点时间限制:  1000ms  内存限制:  131072kB 给定一个只包含加法和乘法的算术表达式,请你编程计算表达式的值. 输入 输入仅有一行 ...

  5. 【Zhejiang University PATest】02-3. 求前缀表达式的值

    算术表达式有前缀表示法.中缀表示法和后缀表示法等形式.前缀表达式指二元运算符位于两个运算数之前,例如2+3*(7-4)+8/4的前缀表达式是:+ + 2 * 3 - 7 4 / 8 4.请设计程序计算 ...

  6. 3-07. 求前缀表达式的值(25) (ZJU_PAT数学)

    题目链接:http://pat.zju.edu.cn/contests/ds/3-07 算术表达式有前缀表示法.中缀表示法和后缀表示法等形式.前缀表达式指二元运算符位于两个运算数之前,比如2+3*(7 ...

  7. 重温C语言(1)----计算算术表达式的值

    <C程序设计语言>练习题 5-10 编写程序 expr,计算从命令行输入的逆波兰表达式的值,其中每个运算符或操作数用一个单独的参数表示.例如,命令 expr 2 3 4 + * 计算表达式 ...

  8. [LeetCode] Evaluate Division 求除法表达式的值

    Equations are given in the format A / B = k, where A and B are variables represented as strings, and ...

  9. [LeetCode] 399. Evaluate Division 求除法表达式的值

    Equations are given in the format A / B = k, where A and B are variables represented as strings, and ...

随机推荐

  1. 有向图的拓扑排序的理解和简单实现(Java)

    如果图中存在环(回路),那么该图不存在拓扑排序,在这里我们讨论的都是无环的有向图. 什么是拓扑排序 一个例子 对于一部电影的制作过程,我们可以看成是一个项目工程.所有的工程都可以分为若干个" ...

  2. jpetStore 学习总结(1)

    最近学习了Springmvc4,对官方的例子jpetStore进行了分析研究,在官方网站下载spring-framework-2.5.6.SEC03,其中samples文件夹里就有jpetstore的 ...

  3. Windows开发经验 - Visual Studio 2017

    1. 调试子进程 Visual Studio 2017及更早的版本原生不支持调试子进程,不确定未来是否会支持.可以通过官方插件让Visual Studio能够调试子进程. https://market ...

  4. jquery中ajax使用error调试错误的方法,实例分析了Ajax的使用方法与error函数调试错误的技巧

    代码:$(document).ready(function() { jQuery("#clearCac").click(function() { jQuery.ajax({ url ...

  5. json对象和数组

    Json数据就是格式化的字符串.c#中如果做为参数调用,类型就是string.1.Json数组 方括号[{ "firstName":"John" , " ...

  6. html-css-js基本理解和简单总结

    目录 一.对于网页的基本理解 1.网页是一种数据展示和信息交互的载体 2.网页组成部分 3.支撑一个网页的技术模块 二.html的理解和技术笔记 1.html理解 2.html技术笔记-html标签 ...

  7. MFRC522开发笔记

    一.了解基本概念 ①ISO-14443A协议:( 国际标准化组织:International Organization for Standardization)RFID协议的一种;   PICC:临近 ...

  8. hibernate多对一单向关联注解方式

    多对一单向关联,在多的一方加上一的一方作为外键.在程序里表现为:在多的一方加上一的引用. 小组类Group,用户User: Group: package com.oracle.hibernate; i ...

  9. Git fetch & pull 区别

    1 简单概括 2 git fetch 的用法 3 git pull的用法 文章来源:https://blog.csdn.net/qq_36113598/article/details/78906882

  10. centos6 vps部署rails

    centos 6 vps初始化部署rails应用1 ssh登录 vpsssh -p port root@server_ip_address 2 添加用户 adduser usernamepasswd ...