不使用逆波兰式计算四则数学表达式

        一个符合人类自然书写习惯的数学表达式,称为中缀表达式,即四则运算

                举个例子:2+(-2*3)+((4+5)-(6-7))/2= 1.0

        不过中缀表达式充斥着复杂且无规律的运算优先级,是不能直接从左到右计算的,而计算器也无法像人类那样“先乘除,后加减”,更别说小括号。但人类也不能离开计算器,精密领域的带六七位小数的运算、大数计算、超多项表达式(A1 op A2 op op An ,op∈{ + , - , * , / , ( , ) }  )的运算,人力运算效率极低,没有计算器就没有现代的人类文明。

        既然计算器不能从左到右计算中缀表达式,那就要设计一个算法帮助计算器计算数学表达式。目前绝大多数计算器用的是逆波兰表达式算法:将正常的中缀表达式转化为后缀表达式(逆波兰式),再对逆波兰式进行从左到右的计算。

        所以科学家早就发明了计算器能够使用的算法,那还有没有算法能够替代逆波兰表达式算法呢?肯定有,当然逆波兰表达式算法是效率最优的算法了,但也存在让计算器直接计算中缀表达式的算法。

        我的设想是:既然中缀表达式存在不同的优先级模块,像一条直线有了凸起,那就多次处理,把凸起的部分撸直。实际操作总结:递归消括号,循环消乘除,再循环一轮处理加减,就能计算一个简单的四则表达式(A1 op A2 op op An ,op∈{ + , - , * , / , ( , ) }  

        用Java完成了实践,欢迎出样例挑错(没做太多的测试,使用的数据在double范围内)

public class Main {

    /**
     * 3+(-2*3)+[(4+5)-(6-7)]/2=  2.0
     * 2+(-2*3)+((4+5)-(6-7))/2=  1.0
     * 1+(-2*3)+((4+5)-(6-7)]/2=  error
     * 1-2+3*(4+5)/6=  3.5
     * 100*100/50+200*0.05-100000=  -99790.0
     * ((4+5)-(6-7))/2= 5.0
     * -100*100+900+1100+1000/100*10*100= 2000.0
     */


    public static boolean error = false;

    public static double compute(String expression) {
        String s = "";
        double result = 0.0;
        int len = expression.length();
        LinkedList<String> a = new LinkedList<>();//分割并存放第一次运算的数据
        //首先,将一个多项表达式字符串中的各个项分割出来,从左到右分割,依次放进链表a
        for(int i=0;i<len;i++) {
            if(error) {
                return 0;
            }
            char c = expression.charAt(i);
            if(c>='0'&&c<='9') {
                s += c;
                if(i==len-1) {
                    a.addLast(s);
                }
            }
            else {
                //存放运算符号以及括号()[]
                if(!s.equals("")) {
                    if(c=='.') {
                        s += c;
                    }
                    else {
                        a.addLast(s);
                        s = "";
                        a.addLast(c+"");
                    }
                }
                else if((s.equals("")&&c=='-')) {
                    //记录负数
                    s += c;
                }
                else if(s.equals("")&&c!='-') {
                    //判断出现 (1+2) 的情况
                    a.addLast(c+"");
                }
            }
        }

        //错误特判,最后一个非数字的元素只能是 ) ],这个特判能排除 1. 2. 0.等情况
        String last = a.getLast();
        len = last.length();
        if(!(last.charAt(len-1)>='0'&&last.charAt(len-1)<='9')) {
            char c = last.charAt(len-1);
            if(c!=')'&&c!=']') {
                error = true;
                return 0;
            }
        }

        //手动为a链表添加一个结束判断符,用于处理最后一个项
        a.addLast("=");
        int size = a.size(),bp = 0;
        LinkedList<String> b = new LinkedList<>();//存放第二次运算的数据,只存放数字与+、-
        LinkedList<Character> brackets = new LinkedList<>();//存放括号的栈
        int flag = 0;//记录出现第几个括号
        LinkedList<String> subexpress = new LinkedList<>();//括号内子表达式

        String express = "";//用于递归形参

        //第一轮计算,计算括号和乘除。括号内的表达式抽取出来递归计算,所有乘除运算在本循环内完成
        for(int i=0;i<size;i++) {
            String as = a.get(i);
            char c = as.charAt(0);//符号的字符串都只有一位,只需要截取下标0的字符即可
            //如果flag为0,说明暂无记录到左括号或者左括号已被消除
            if (flag==0) {
                if(bp>2&&(b.get(bp-2).charAt(0)=='*'||b.get(bp-2).charAt(0)=='/')) {
                    //回溯处理乘除是为了适应a*(b+c)的情况
                    //最后一个=是为了处理边界的情况
                    double b2 = Double.parseDouble(b.removeLast());
                    char op = b.removeLast().charAt(0);
                    double b1 = Double.parseDouble(b.removeLast());
                    bp = bp-3;
                    if(op=='*') {
                        b.addLast((b1*b2)+"");
                    }
                    else {
                        if((b2-0)<1e-5) {
                            error = false;
                            return 0;
                        }
                        b.addLast((b1/b2)+"");
                    }
                    bp++;
                }
                if(i==size-1) {
                    break;
                }
                if(c=='('||c=='[') {
                    //准备用subexpress[i]代替express
                    flag++;
                    subexpress.addLast("");
                    b.addLast(express);
                    express = "";
                    brackets.addLast(c);
                }
                else if(c=='+'||c=='-'||c=='*'||c=='/') {
                    b.addLast(as);
                    bp++;
                }
                else {
                    b.addLast(as);
                    bp++;
                }
            }
            else {
                //flag>0一律进入这个部分,这个部分不进行运算,只记录子表达式
                if(i==size-1) {
                    //如果该方法传入的多项式已遍历到= ,但仍有左括号未消除,报错并强制返回0
                    error = true;
                    return 0;
                }
                if(brackets.getLast()==c) {
                    //括号栈只能存在一种左括号,第二种左括号为子表达式的一部分
                    //如果遇到相同的左括号,可视为在子表达式中出现了子表达式:((a-b)*c)=
                    flag++;
                    subexpress.addLast("");//子表达式链表后新增一个空字符串记录新的子表达式
                    brackets.addLast(c);
                }
                else {
                    if((brackets.getLast()=='('&&c==')')||(brackets.getLast()=='['&&c==']')) {
                        flag--;
                        //( )与[ ]可以相互抵消一次
                        brackets.removeLast();
                        //左括号已经抵消,则将子表达式送入递归
                        if(!brackets.isEmpty()) {
                            //如果左括号栈不为空,说明该子表达式的值为上级子表达式的一个项
                            subexpress.set(flag-1,subexpress.get(flag-1)+compute(subexpress.get(flag)));
                            subexpress.removeLast();
                        }
                        else {
                            //左括号栈为空,可以将最后一个子表达式的值作为总表达式的一个项
                            b.set(bp,compute(subexpress.get(flag))+"");
                            bp++;
                            subexpress.removeLast();
                        }
                    }
                    else {
                        //非左、右括号,直接加入当前子表达式字符串末
                        subexpress.set(flag-1,subexpress.get(flag-1)+as);
                    }
                }
            }

        }

        //第二轮运算,计算加减,此时链表b只存在 数字字符串 以及 "+" "-",可以从左到右计算
        result = Double.parseDouble(b.getFirst());
        for(int i=0;i<bp;i++) {
            if(i==0) {
                continue;
            }
            char c = b.get(i).charAt(0);
            if(c=='+'||c=='-') {
                //经过前面的处理,正确的四则表达式最后一定是数字结尾,因此不在意越界的情况
                double b1 = Double.parseDouble(b.get(++i));
                if(c=='+') {
                    result += b1;
                }
                else {
                    result -= b1;
                }
            }
        }

        return result;
    }


    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        //输入类似于 1-2+3*(4+5)/6= 后面以=结束,减少误差
        String expression = scanner.next();
        //去除最后一个=
        expression = expression.substring(0,expression.length()-1); //去掉 =
        double result = compute(expression);
        if(error) {
            System.out.println("error");
            return;
        }
        System.out.println(result);

    }

}

        小结:写完了这个算法,深刻体会了逆波兰表达式算法的强大之处,对伟大的科学家先驱抱有崇高的敬意。这个算法为了处理各种bug以及避免错误的条件(除以0、括号后接括号、小数等等),添加了不少特判,所以我觉得实用性不强,很弱。纯纯地写来玩玩,所以,放数据可以、提意见可以、友善交流可以,喷写的烂就不必了emmmmm。

        这个函数只用到了一个全局变量,因此模块化做得还行,属于是cv大法即插即用,觉得能用得上的就cv吧。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值