逆波兰计算器

深藏阁楼爱情的钟 2022-12-23 07:09 206阅读 0赞

该版本只支持整数类型数进行计算

本文主要是对逆波兰计算器的原理介绍和代码实现,所以对前缀、中缀、后缀表达式不做过多的赘述,下面先简单介绍一下后缀表达式

后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后

20201123191345158.png

举例: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 –

后缀表达式的计算机求值

从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为

表达式的结果 例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:

1 左至右扫描,将3和4压入堆栈;

2 遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;

3 将5入栈;

4 接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;

5 将6入栈;

6 最后是-运算符,计算出35-6的值,即29,由此得出最终结果

不难看出,逆波兰计算器的重点不是计算,而是将用户输入的中缀表达式转换为后缀表达式

中缀表达式转换成后缀表达式的步骤

1 初始化两个栈:运算符栈s1和储存中间结果的栈s2;

2 从左至右扫描中缀表达式;

3 遇到操作数时,将其压s2;

4 遇到运算符时,比较其与s1栈顶运算符的优先级:

  1. (1) 如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
  2. (2) 否则,若优先级比栈顶运算符的高,也将运算符压入s1
  3. (3) 否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较;

5 遇到括号时:

  1. (1) 如果是左括号“(”,则直接压入s1
  2. (2) 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃

6 重复步骤2至5,直到表达式的最右边

7 将s1中剩余的运算符依次弹出并压入s28 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

下面看一个实例

由用户输入 1+((2+3)*4)-5表达式,计算对应的结果

将1+((2+3)*4)-5表达式转换为后缀表达式

初始化两个栈,运算符栈s1和储存中间结果的栈s2,设置一个指针,从中缀表达式的左边开始向右边扫描

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Nzk2MjA4_size_16_color_FFFFFF_t_70

扫描到1,1是操作数,所以直接压入s2栈中

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Nzk2MjA4_size_16_color_FFFFFF_t_70 1

继续扫描,扫描到“+”,是运算符,但是因为s1栈为空,所以直接压入s1栈

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Nzk2MjA4_size_16_color_FFFFFF_t_70 2

继续扫描,扫描到两个“(”,直接压入s1栈

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Nzk2MjA4_size_16_color_FFFFFF_t_70 3

继续扫描,扫描到2,操作数,直接压入s2栈

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Nzk2MjA4_size_16_color_FFFFFF_t_70 4

继续扫描,扫描到“+”,因为s1栈顶元素不是运算符,所以继续压栈

继续扫描,扫描到操作数,直接压入s2栈

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Nzk2MjA4_size_16_color_FFFFFF_t_70 5

继续扫描,扫描到右括号,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Nzk2MjA4_size_16_color_FFFFFF_t_70 6

继续扫描,扫描到*,因为s1栈顶元素不是运算符,所以继续压栈

继续扫描,扫描到4,直接压入s2栈

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Nzk2MjA4_size_16_color_FFFFFF_t_70 7

继续扫描,扫描到扫描到右括号,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Nzk2MjA4_size_16_color_FFFFFF_t_70 8

继续扫描,扫描到“-”,因为此时栈顶是运算符,目前元素的优先接不高于(<=)栈顶运算符优先级,所以将s1栈顶的运算符弹出并压入到s2中,再次判断目前元素与s1中新的栈顶运算符相比较

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Nzk2MjA4_size_16_color_FFFFFF_t_70 9

此时栈顶没有元素,所以将“-”压入s1

继续扫描,扫描到5,直接压入s2栈

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Nzk2MjA4_size_16_color_FFFFFF_t_70 10

此时,扫描就结束了,所以只需要将s1栈中的运算符一次压入s2中即可

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Nzk2MjA4_size_16_color_FFFFFF_t_70 11

此时,s2栈中就是后缀表达式的逆序1 2 3 + 4 × + 5 –

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1Nzk2MjA4_size_16_color_FFFFFF_t_70 12

下面看代码实现

由用户输入 1+((2+3)*4)-5表达式,首先先将用户输入的表达式转成中缀表达式对应的List

  1. /**
  2. * 将中缀表达式字符串转成对应的List
  3. * @param s 输入字符串
  4. * @return
  5. */
  6. public static List<String> toInfixExpresstionList(String s){
  7. List<String> ls = new ArrayList<>();
  8. //索引,用于遍历中缀表达式字符串
  9. int i = 0;
  10. //用于多位数字的拼接
  11. String str;
  12. //每遍历到一个字符,就放入c中
  13. char c;
  14. do{
  15. //如果c是一个符号,直接拼接
  16. if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57){
  17. ls.add("" + c);
  18. i++;
  19. }else{
  20. //扫描到的是一个数字
  21. str = "";
  22. //考虑多位数
  23. while(i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57){
  24. str += s.charAt(i);
  25. i++;
  26. }
  27. ls.add(str);
  28. }
  29. }while(i < s.length());
  30. return ls;
  31. }

然后将中缀表达式的List集合转换成后缀表达式的List集合

代码优化

这里s2没有使用栈数据结构,因为s2整个过程中没有出栈的操作,使用ArrayList,不需要反转

因为这个过程需要判断优先级,所以创建一个Operation类返回运算符的优先级

  1. class Operation{
  2. private static int ADD = 1;
  3. private static int SUB = 1;
  4. private static int MUL = 2;
  5. private static int DIV = 2;
  6. public static int getValue(String ch){
  7. int res = 0;
  8. switch(ch){
  9. case "+":
  10. res = ADD;
  11. break;
  12. case "-":
  13. res = SUB;
  14. break;
  15. case "*":
  16. res = MUL;
  17. break;
  18. case "/":
  19. res = DIV;
  20. break;
  21. default:
  22. System.out.println("没有匹配到该字符");
  23. break;
  24. }
  25. return res;
  26. }
  27. }
  28. /**
  29. * 中缀表达式转后缀表达式
  30. * @param ls 中缀表达式
  31. * @return
  32. */
  33. public static List<String> parseSuffixExpresstion(List<String> ls){
  34. //定义两个栈
  35. Stack<String> s1 = new Stack<>();
  36. //因为整个过程中s2没有发生过弹栈,所以将s2栈换成一个List
  37. //Stack<String> s2 = new Stack<>();
  38. List<String> s2 = new ArrayList<>();
  39. for(String item : ls){
  40. if(item.matches("\\d+")){//数字
  41. s2.add(item);
  42. }else if(item.equals("(")){//如果是左括号“(”,则直接压入s1
  43. s1.push(item);
  44. }else if(item.equals(")")){//如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
  45. while(!s1.peek().equals("(")){
  46. s2.add(s1.pop());
  47. }
  48. s1.pop();// 将“(”弹栈
  49. }else{//遇到运算符时,比较其与s1栈顶运算符的优先级:
  50. while(s1.size() != 0 && Operation.getValue(item) <= Operation.getValue(s1.peek())){
  51. s2.add(s1.pop());
  52. }
  53. s1.push(item);
  54. }
  55. }
  56. //将s1中剩余的运算符依次弹出并压入s2
  57. while(s1.size() !=0){
  58. s2.add(s1.pop());
  59. }
  60. return s2;
  61. }

根据后缀表达式来计算结果(本文的一开始,已经将该部分的思路阐述清楚了)

  1. /**
  2. * 将List集合入栈,并计算值
  3. * @param ls
  4. * @return
  5. */
  6. public static int calculate(List<String> ls){
  7. Stack<String> stack = new Stack<String>();
  8. for(String item : ls){
  9. if (item.matches("\\d+")){
  10. stack.push(item);
  11. }else{
  12. int num1 = Integer.parseInt(stack.pop());
  13. int num2 = Integer.parseInt(stack.pop());
  14. int res = 0;
  15. switch(item){
  16. case "+" :
  17. res = num1 + num2;
  18. break;
  19. case "-" :
  20. res = num2 - num1;
  21. break;
  22. case "*" :
  23. res = num1 * num2;
  24. break;
  25. case "/" :
  26. res = num2 / num1;
  27. break;
  28. default :
  29. throw new RuntimeException(item + "符号不匹配");
  30. }
  31. stack.push("" + res);
  32. }
  33. }
  34. return Integer.parseInt(stack.pop());
  35. }

以上就是逆波兰计算器的实现全过程,代码仍不完整

需要优化的部分:

支持 + - * / ( )

多位数,支持小数,

兼容处理, 过滤任何空白字符,包括空格、制表符、换页符

所有代码

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. import java.util.Stack;
  4. public class PolandNotation {
  5. public static void main(String[] args) {
  6. //中缀表达式"1+((2+3)×4)-5"
  7. String expresstion = "1+((20+3)*4)-5";
  8. List<String> infixExpresstion = toInfixExpresstionList(expresstion);
  9. System.out.println("中缀表达式字符串:" + expresstion);
  10. System.out.println("中缀表达式:" + infixExpresstion);
  11. List<String> suffixExpresstion = parseSuffixExpresstion(infixExpresstion);
  12. System.out.println("后缀表达式:" + suffixExpresstion);
  13. int res = calculate(suffixExpresstion);
  14. System.out.println("计算结果 = " + res);
  15. }
  16. /**
  17. * 将后缀表达式转换为ArrayList
  18. * @param suffixExpresstion 后缀表达式
  19. * @return
  20. */
  21. public static List<String> getList(String suffixExpresstion){
  22. String[] split = suffixExpresstion.split(" ");
  23. List<String> list = new ArrayList<String>();
  24. for(String ele : split){
  25. list.add(ele);
  26. }
  27. return list;
  28. }
  29. /**
  30. * 将中缀表达式字符串转成对应的List
  31. * @param s 输入字符串
  32. * @return
  33. */
  34. public static List<String> toInfixExpresstionList(String s){
  35. List<String> ls = new ArrayList<>();
  36. //索引,用于遍历中缀表达式字符串
  37. int i = 0;
  38. //用于多位数字的拼接
  39. String str;
  40. //每遍历到一个字符,就放入c中
  41. char c;
  42. do{
  43. //如果c是一个符号,直接拼接
  44. if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57){
  45. ls.add("" + c);
  46. i++;
  47. }else{
  48. //扫描到的是一个数字
  49. str = "";
  50. //考虑多位数
  51. while(i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57){
  52. str += s.charAt(i);
  53. i++;
  54. }
  55. ls.add(str);
  56. }
  57. }while(i < s.length());
  58. return ls;
  59. }
  60. /**
  61. * 中缀表达式转后缀表达式
  62. * @param ls 中缀表达式
  63. * @return
  64. */
  65. public static List<String> parseSuffixExpresstion(List<String> ls){
  66. //定义两个栈
  67. Stack<String> s1 = new Stack<>();
  68. //因为整个过程中s2没有发生过弹栈,所以将s2栈换成一个List
  69. //Stack<String> s2 = new Stack<>();
  70. List<String> s2 = new ArrayList<>();
  71. for(String item : ls){
  72. if(item.matches("\\d+")){//数字
  73. s2.add(item);
  74. }else if(item.equals("(")){//如果是左括号“(”,则直接压入s1
  75. s1.push(item);
  76. }else if(item.equals(")")){//如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
  77. while(!s1.peek().equals("(")){
  78. s2.add(s1.pop());
  79. }
  80. s1.pop();// 将“(”弹栈
  81. }else{//遇到运算符时,比较其与s1栈顶运算符的优先级:
  82. while(s1.size() != 0 && Operation.getValue(item) <= Operation.getValue(s1.peek())){
  83. s2.add(s1.pop());
  84. }
  85. s1.push(item);
  86. }
  87. }
  88. //将s1中剩余的运算符依次弹出并压入s2
  89. while(s1.size() !=0){
  90. s2.add(s1.pop());
  91. }
  92. return s2;
  93. }
  94. /**
  95. * 将List集合入栈,并计算值
  96. * @param ls
  97. * @return
  98. */
  99. public static int calculate(List<String> ls){
  100. Stack<String> stack = new Stack<String>();
  101. for(String item : ls){
  102. if (item.matches("\\d+")){
  103. stack.push(item);
  104. }else{
  105. int num1 = Integer.parseInt(stack.pop());
  106. int num2 = Integer.parseInt(stack.pop());
  107. int res = 0;
  108. switch(item){
  109. case "+" :
  110. res = num1 + num2;
  111. break;
  112. case "-" :
  113. res = num2 - num1;
  114. break;
  115. case "*" :
  116. res = num1 * num2;
  117. break;
  118. case "/" :
  119. res = num2 / num1;
  120. break;
  121. default :
  122. throw new RuntimeException(item + "符号不匹配");
  123. }
  124. stack.push("" + res);
  125. }
  126. }
  127. return Integer.parseInt(stack.pop());
  128. }
  129. }
  130. class Operation{
  131. private static int ADD = 1;
  132. private static int SUB = 1;
  133. private static int MUL = 2;
  134. private static int DIV = 2;
  135. public static int getValue(String ch){
  136. int res = 0;
  137. switch(ch){
  138. case "+":
  139. res = ADD;
  140. break;
  141. case "-":
  142. res = SUB;
  143. break;
  144. case "*":
  145. res = MUL;
  146. break;
  147. case "/":
  148. res = DIV;
  149. break;
  150. default:
  151. System.out.println("没有匹配到该字符");
  152. break;
  153. }
  154. return res;
  155. }
  156. }

发表评论

表情:
评论列表 (有 0 条评论,206人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java代码实现波兰计算器

    关于逆波兰计算器,需求如下 输入一个逆波兰表达式(后缀表达式),使用栈(Stack),计算其结果,只支持对整数的计算即可。 思路分析 后缀表达式又称逆波兰表达式,与

    相关 波兰计算器

    该版本只支持整数类型数进行计算 本文主要是对逆波兰计算器的原理介绍和代码实现,所以对前缀、中缀、后缀表达式不做过多的赘述,下面先简单介绍一下后缀表达式 后缀表达式又称逆波兰

    相关 波兰计算器

    > 逆波兰表达式又叫做后缀表达式,逆波兰表达式把运算量写在前面,把运算符写在后面。 我们平时书写的表达式都是中缀表达式,这个表达式更加方便我们进行计算。但是相对于计算机而言,

    相关 波兰计算器

    一 整体算法 1 输入一个中缀表达式。 2 将中缀表达式转换为后缀表达式(逆波兰表达式)。 3 根据逆波兰表达式求算最后的结果。 二 将中缀表达式转换为后缀表达式

    相关 波兰计算器完整版

    一 需求 完整版的逆波兰计算器,功能包括如下: 1 支持 + - \ / ( ) 2 多位数,支持小数 3 兼容处理, 过滤任何空白字符,包括空格、制表符、换页符