一个算数表达式的前缀中缀和后缀(可进行负数运算)

6 篇文章 0 订阅

一个算数表达式的前缀中缀和后缀

目录

1. 关于前缀、中缀和后缀

  1. 中缀表达式:就是我们常见的算数表达式,有优先级和括号,例如:3+4*(4+5 )。这个对于我们来说很好理解,但是对于计算机来说就比较麻烦。
  2. 前缀表达式:前缀表达式是一种没有括号的算术表达式,与中缀表达式不同的是,其将运算符写在前面,操作数写在后面。为纪念其发明者波兰数学家Jan Lukasiewicz,前缀表达式也称为“波兰式”。例如,- 1 + 2 3,它等价于1-(2+3)。
  3. 后缀表达式:不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则,如:(2 + 1) * 3 , 即2 1 + 3 *

2. 中缀转前缀、后缀的算法思想及代码实现

2.1 中缀转前缀

中缀转前缀算法思想:
1. 初始化两个栈s1,s2
2. 从右向左扫描中缀表达式,若是数字,将数字压入栈s1
3. 若遇到运算符,则将其与栈s2栈顶的运算符优先级作比较
3.1. 若栈s2为空,则将其压入栈
3.2. 如果栈s2不为空,且不是右括号,且和栈s2的栈顶的运算符优先级相同或比其高,则将这个运算符压入栈s2
3.3. 如果遇到的运算符比栈顶运算符优先级低,则将从s1 中弹出两个数字与s1栈顶的运算符做运算,将结果压入栈s1,然后回到3.1
4. 如果遇到右括号,将右括号压入栈s2
5. 如果遇到左括号,则s2栈顶元素与右括号做比较,如果不是则从s1 中弹出两个数字与s1栈顶的运算符做运算,将结果压入栈s1。
6. 如果s2不为空,则从s1 中弹出两个数字与s1栈顶的运算符做运算,将结果压入栈s1,直到s2为空
7. 如果最终结果中只有栈s1只有一个元素,则这个结果即为正确结果。
中缀转前缀的代码实现如下图所示:

import java.math.BigDecimal;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Scanner;
import java.util.Stack;

public class CenterToFront {


    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.print("请输入:");
            String s = sc.nextLine();
            String result = toFront(s);
            if (result != "出错") {
                System.out.println("结果是:" + result);
            }
            else {
                System.out.println(s);
            }
        }
    }
    static String operator = "+-*/";    // 用于判断是否是操作符
    // 用于判断优先级
    static HashMap<Character, Integer> priority = new HashMap<>();
    static {
        priority.put('+', 1);
        priority.put('-', 1);
        priority.put('*', 2);
        priority.put('/', 2);
        priority.put(')', 0);
    }

    public static String toFront (String str) {
        ArrayDeque<Character> s1 = new ArrayDeque<>();  // 用于储存运算符
        ArrayDeque<Double> s2 = new ArrayDeque<>();     // 用于储存数字
        // 这里的这个栈不使用ArrayDeque是
        // 因为当ArrayDeque存入Object元素时,
        // 会将同一种类型的元素放在一块,这是一个坑点
        Stack<Object> s3 = new Stack<>();
        char ch, temp;      // ch储存每次遍历时,temp后面用于暂时储存字符,后面会用到
        // 用于储存数字的索引,从右向左遍历是frontDouble存储的是数字
        int frontDouble = -1;
        int len = str.length(); // 用于储存字符串的字符串的长度
        for (int i = len - 1; i >= 0; i--) {
            ch = str.charAt(i); // charAt()方法可以返回字符串索引处的字符

            if (Character.isDigit(ch)) {    // Character的isDigit()可以用来判断数字是否是字符
                frontDouble = readFrontDouble(str, i);  // 该方法用于算该数字从索引处到往前遍历找到第一个非数字的索引
                if (frontDouble == -1) {
                    return "出错";
                }
                // String类的substring(int a, int b)用于返回该字符串从a到b-1处的一个子字符串
                // Double.parseDouble(String str) 将一个自负串解析成一个double型数字
                double d = Double.parseDouble(str.substring(frontDouble, i + 1));
                if ((int)d == d)
                    s3.add(String.valueOf((int)d));
                else
                    s3.add(String.valueOf(d));
                s2.push(d);
                i = frontDouble;
            }
            /**
             * 查看ch是否是运算符
             * 若是运算符则与s1栈顶的运算符作比较
             *      1. 若栈顶元素为空,则将ch压入栈s1
             *      2. 若栈顶元素不为空,且ch的优先级比s1栈顶优先级高
             *          则将ch压入栈s1
             *      3. 若栈顶元素不为空,且ch的优先级比s1栈顶优先级低
             *          则从栈s2中弹出两个数、从栈s1中弹出一个运算符,
             *          然后做运算,将元素所得的结果压入栈s2
             */
            else if (operator.indexOf(ch) != -1) {
                while (!s1.isEmpty() && s1.peek() != ')'
                        && priority.get(ch) < priority.get(s1.peek())) {
                    double d1 = s2.pop();
                    double d2 = s2.pop();
                    s3.push(s1.peek().toString());
                    s2.push(cal(d1, d2, s1.pop()));
                }
                s1.push(ch);
            }
            // 如遇到右括号,直接压入栈
            else if (ch == ')')
                s1.push(ch);
            /**
             * 如果遇到左括号,并且s1栈顶元素不是右括号
             * 则从s2栈顶弹出两个数字,从s1栈顶弹出一个运算符
             * 做运算,将结果存入栈s2
             * 循环上述过程知道s1栈顶元素是左括号,然后将左括号弹出
             *
             */
            else if (ch == '(') {
                while (s1.peek() != ')') {
                    double d1 = s2.pop();
                    double d2 = s2.pop();
                    s3.push(s1.peek().toString());
                    s2.push(cal(d1, d2, s1.pop()));
                    // 如果没遇到左括号,但s1栈已经是空的了,那么肯定出错了
                    if (s1.isEmpty()) {
                        return "出错";
                    }
                }
                s1.pop();
            }
            // 忽略掉空格
            else if (ch == ' ') {
                continue;
            }
            // 有其他字符肯定出错
            else {
                return "出错";
            }
        }
        /**
         * 字符串遍历完成后若s1栈不为空(一般来说坑定不为空)
         * 则从栈s1中弹出一个符号,从s2栈顶弹出两个数字做运算
         * 后压入栈s2,直到栈s1为空
         */

        while (!s1.isEmpty()) {
            double d1 = s2.pop();
            double d2 = s2.pop();
            s3.push(s1.peek().toString());
            double d3 = cal(d1, d2, s1.pop());
            s2.push(d3);
        }
        System.out.print("前缀是:");
        while(!s3.isEmpty()) {
            System.out.print(s3.pop() + " ");
        }
        System.out.println();
        // 若最后栈s2中还有超过一个元素,则证明出错了
        double result = s2.pop();
        if (!s2.isEmpty())
            return "出错";
        if ((int) result == result)
            return String.valueOf((int)result);
        else{
            return String.valueOf(result);
        }

    }
    /**
    * 此方法用于计算的d1 op d2 的结果
    */ 
    private static double cal(double d1, double d2, char op)
            throws ArithmeticException{
        switch (op) {
            case '+':
                return d1 + d2;
            case '-':
                return d1 - d2;
            case '*':
                return d1 * d2;
            case '/':
                if (d1 == 0) {
                    return 1;
                }
                return d1 / d2;
        }

        return 1;
    }
    /**
    * 这是一个读取数字位置的方法
    * 该方法可以从右向左的读取一个数字,然后返回该数字在字符串中开始的下标
    */ 
    private static int readFrontDouble(String str, int start) {
        int flag = -1;    // 用于记录小数点
        char ch;          // 用于记录每次遍历的字符
        for (int i = start; i >= 0; i--) {
            ch = str.charAt(i);
            if (ch == '.') {      //如果第一次出现小数点,则记录小数点位置,如果不是那么肯定出错
                if (flag != -1) {
                    return -1;
                }
                else {
                    flag = i;
                }
            // 如果该字符是减号,若该字符是第一位(i == 0),则该减号是负号,或者如果该字符的前一个字符不是数字,证明也是负号
            } else if (ch == '-' && ((i > 0
                    && !Character.isDigit((str.charAt(i-1))))
                    || i == 0)) {
                return i;
                // 如果是非数字的肯定该数字已经找到了
            }else if (!Character.isDigit(ch))
                return i + 1;
            else if (i == 0) {
                return 0;
            }
        }
        return -1;
    }
}

2.2 中缀转后缀

中缀转后缀的计算方法与中缀转前缀的方法特别相似,只是中缀转后缀是从前到后的。
1. 初始化两个栈s1,s2
2. 从左向右扫描中缀表达式,若是数字,将数字压入栈s1
3. 若遇到运算符,则将其与栈s2栈顶的运算符优先级作比较
3.1. 若栈s2为空,则将其压入栈
3.2. 如果栈s2不为空,且不是左括号,且和栈s2的栈顶的运算符优先级相同或比其高,则将这个运算符压入栈s2
3.3. 如果遇到的运算符比栈顶运算符优先级低,则将从s1 中弹出两个数字与s1栈顶的运算符做运算,将结果压入栈s1,然后回到3.1
4. 如果遇到左括号,将左括号压入栈s2
5. 如果遇到右括号,则s2栈顶元素与左括号做比较,如果不是则从s1 中弹出两个数字与s1栈顶的运算符做运算,将结果压入栈s1。
6. 如果s2不为空,则从s1 中弹出两个数字与s1栈顶的运算符做运算,将结果压入栈s1,直到s2为空
7. 如果最终结果中只有栈s1只有一个元素,则这个结果即为正确结果。

// 前缀转中缀的注释已经相当的清晰了,所以这里就不在赘述了
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Scanner;
import java.util.Stack;


public class CenterToLast {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in); 
        while(true) {
            System.out.println("请输入:");
            String str = sc.nextLine();
            System.out.println(toSuffix(str));
        }
    }

    static String operator = "+-*/";    // 用于判断是否是操作符
    // 用于判断优先级
    static HashMap<Character, Integer> priority = new HashMap<>();
    static {
        priority.put('+', 1);
        priority.put('-', 1);
        priority.put('*', 2);
        priority.put('/', 2);
    }


    private static String toSuffix(String str)  {  
        int len = str.length();  
        char c, tempChar;  
        ArrayDeque<Character> s1 = new ArrayDeque<Character>();  
        ArrayDeque<Double> s2 = new ArrayDeque<Double>();  
        Stack<Object> s3 = new Stack<>();
        double number;  
        int lastIndex = -1;  
        for (int i = 0; i < len; ++i) {
            c = str.charAt(i);  
            if (Character.isDigit(c)) {  
                lastIndex = readDouble(str, i);  
                number = Double.parseDouble(str.substring(i, lastIndex));  
                s2.push(number);  
                i = lastIndex - 1;  
                if ((int) number == number)  
                    s3.push((int)number);
                else  
                    s3.push(number);  
            }else if (c == '-' && i == 0) {
                lastIndex = readDouble(str, i + 1);  
                number = Double.parseDouble(str.substring(i, lastIndex));  
                s2.push(number);  
                i = lastIndex - 1;  
                if ((int) number == number)  
                    s3.push((int) number);  
                else  
                    s3.push(number);
            }else if (i > 0 && c == '-' &&
                    (str.charAt(i-1) == '(' 
                || operator.indexOf(str.charAt(i-1)) != -1)) {
                lastIndex = readDouble(str, i + 1);  
                number = Double.parseDouble(str.substring(i, lastIndex));  
                s2.push(number);  
                i = lastIndex - 1;  
                if ((int) number == number)  
                    s3.push((int)number); 
                else  
                    s3.push(number);
            }
            else if (operator.indexOf(c) != -1) {  
                while (!s1.isEmpty() && s1.peek() != '('  
                        && priority.get(c) < priority.get(s1.peek())) {  
                    System.out.print(s1.peek() + " ");  
                    double num1 = s2.pop();  
                    double num2 = s2.pop();
                    s3.push(s1.peek());
                    s2.push(calc(num2, num1, s1.pop()));  
                }  
                s1.push(c);  
            } else if (c == '(') {  
                s1.push(c);  
            } else if (c == ')') {  
                while ((tempChar = s1.pop()) != '(') {  
                    System.out.print(tempChar + " ");  
                    double num1 = s2.pop();  
                    double num2 = s2.pop(); 
                    s3.push(s1.peek());
                    s2.push(calc(num2, num1, tempChar));  
                    if (s1.isEmpty()) {  
                        return "出错";
                    }  
                }  
            } else if (c == ' ') {  
                    continue; 
            } else {  
                return "出错"; 
            }  
        }  
        while (!s1.isEmpty()) {  
            tempChar = s1.pop();  
            s3.push(tempChar);  
            double num1 = s2.pop();  
            double num2 = s2.pop();  
            s2.push(calc(num2, num1, tempChar));  
        }  
        double result = s2.pop();  
        if (!s2.isEmpty())  
            return "出错";
        System.out.print("后缀是:");
        while (!s3.isEmpty()) {
            System.out.print(s3.pop() + " ");
        }
        System.out.println();
        if ((int) result == result)  
            return String.valueOf(((int)result)); 
        else  
            return String.valueOf(result);  
    }

    /**
     * 获取是double值得最后一位索引
     */
    public static int readDouble(String str, int start) {
        int len = str.length();
        int dotIndex = -1;
        char ch;
        for (int i = start; i < len ; i++) {
            ch = str.charAt(i);
            if(ch == '.') {
                if(dotIndex != -1) 
                    return -1;
                else if(i == len - 1) 
                    return -1;
                else
                    dotIndex = i;
            } else if(!Character.isDigit(ch)) {
                if (dotIndex == -1 || i - dotIndex > 1)
                    return i;
                else
                    return -1;
            }
            else if(i == len - 1) {
                return len;
            }
        }
        return -1;
    }

    /**
     * 计算两个数的结果
     * 并返回
     */
    private static double calc (double num1, double num2, char op) 
        throws IllegalArgumentException{
        switch (op) {
        case '+':
            return num1 + num2;
        case '-':
            return num1-num2;
        case '*':
            return num1 * num2;
        case '/':
            if (num2 == 0) 
                throw new ArithmeticException("除数不能为0");
            return num1 / num2;
        default:
            return 0;
        }

    }
}
  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值