算法(3)简单四则运算

作者: hard_man | 来源:发表于2019-04-08 10:28 被阅读1次

    1.0 问题描述

    实现10以内四则运算(只包含数字,+-*/和小括号)

    2.0 问题分析

    1. 四则运算使用“后缀表达式”算法来计算,后缀表达式可以无需考虑运算符优先级,直接从左至右依次计算。
    2. 问题分解成2部分,一是将“中缀表达式”(我们正常写的四则运算字符串样式,即我们的输入表达式)转为“后缀表达式”;二是使用“后缀表达式”求值。
    3. “中缀表达式”转“后缀表达式”流程:
      • 首先建立一个栈和一个队列,分别为“符号栈”和“结果队列”:“符号栈”用于保存符号,处理符号优先级;“结果队列”用于保存“后缀表达式”
      • 依次遍历“中缀表达式”的字符。
        • 若是数字直接进结果队列。
        • 若是运算符,则需查看符号栈顶元素。
          • 如果栈为空或栈顶元素为“(”则直接进符号栈。
          • 否则,需要将符号栈顶部所有优先级低于当前运算符的所有符号依次出栈,进入结果队列。
        • 若是“)”,重复弹出栈顶运算符,进入结果队列,直到遇到“(”,将其丢弃。
      • 遍历完成后,即生成了“后缀表达式”,求值过程如下:
        • 创建一个“数字栈”,用于保存计算结果。
        • 结果队列依次出队列。
          • 若是数字直接进“数字栈”。
          • 若是运算符,则从“数字栈”弹出2个数值,与此运算符计算后,再进“数字栈”。
          • 重复上面的过程,直到“数字栈”剩余1个数字。这个数字即为计算结果。

    3.0 代码实现

    3.1使用swift实现

    /**
     * 把Character转换成Doublec
     * @param {Character} c - 需要转换的字符
     * @return {Double} 返回c对应的数字
     */
    func _c2n(_ c: Character) throws -> Double{
        guard case "0" ... "9" = c else {
            throw AlgorithmError.msg("符号范围错误");
        }
        let zero = "0".unicodeScalars.first!.value;
        return Double(c.unicodeScalars.first!.value - zero);
    }
    
    /**
     * 最小单元计算 {n1}{mark}{n2}
     * @param {Double} n1 - 数字1
     * @param {Double} n2 - 数字2
     * @param {Character} mark - 符号
     * @return {Double} 返回 {n1}{mark}{n2} 的结果
     */
    func _eval(_ n1: Double, _ n2: Double, _ mark: Character) throws -> Double{
        switch mark {
        case "+":
            return n1 + n2;
        case "-":
            return n1 - n2;
        case "*":
            return n1 * n2;
        case "/":
            if n2 == 0 {
                throw AlgorithmError.msg("除0错误");
            }
            return n1 / n2;
        default:
            throw AlgorithmError.msg("符号错误");
        }
    }
    
    /**
     * 比较符号
     * @param {String} a - 符号a
     * @param {String} b - 符号b
     * @return {Bool} 如果a的优先级<=b的优先级返回true,否则返回false
     */
    func _arithmeticCompare(_ a: Character, _ b: Character) throws -> Bool {
        switch b {
        case "+", "-":
            return a == "+" || a == "-";
        case "*", "/":
            return true;
        case "(":
            return false;
        default:
            throw AlgorithmError.msg("非法符号");
        }
    }
    
    
    /**
     * 计算10以内的四则运算
     * @param {String} str - 输入中缀表达式
     * @return {Double} - 返回计算结果
     */
    func arithmetic(_ str: String) throws -> Double{
        //结果栈
        let retStack = Stack<Character>();
        //符号栈
        let markStack = Stack<Character>();
        var lastC: Character? = nil;
        for c in str{
            switch c{
            case "(":
                if let lc = lastC, case "0"..."9" = lc{
                    throw AlgorithmError.msg("发现错误");
                }
                //直接进符号
                markStack.push(c);
                break
            case ")":
                //依次出栈直到括号结束
                while markStack.top() != "(" && markStack.count() > 0 {
                    if let m = markStack.pop(){
                        retStack.push(m);
                    }
                }
                if markStack.count() > 0 {
                    markStack.pop();
                }else{
                    throw AlgorithmError.msg("发现错误");
                }
                break
            case "+", "-", "*", "/":
                //比c优先级高的都进结果栈
                if let t = markStack.top(){
                    while true {
                        if let c = try? _arithmeticCompare(c, t), c{
                            markStack.pop();
                            retStack.push(t);
                            if markStack.count() == 0{
                                break;
                            }
                        }else{
                            break;
                        }
                    }
                }
                markStack.push(c);
                break;
            case "0"..."9":
                if let lc = lastC , case "0"..."9" = lc {
                    throw AlgorithmError.msg("发现错误");
                }
                //直接进结果
                retStack.push(c);
                break;
            default:
                throw AlgorithmError.msg("发现错误");
            }
            lastC = c;
        }
        
        //符号栈可能不空
        while markStack.count() > 0 {
            retStack.push(markStack.pop()!);
        }
        
        //计算结果
        let numStack = Stack<Double>();
        while retStack.count() > 0 {
            if let c = retStack.shift(){
                if case "0"..."9" = c {//数字
                    numStack.push(try _c2n(c));
                }else{//符号
                    if numStack.count() >= 2{
                        if let n1 = numStack.pop(), let n2 = numStack.pop() {
                            numStack.push(try _eval(n2, n1, c));
                        }
                    }else{
                        throw AlgorithmError.msg("发现错误");
                    }
                }
            }
        }
        
        if numStack.count() != 1{
            throw AlgorithmError.msg("发现错误");
        }
        
        return numStack.pop()!;
    }
    

    3.2使用js实现

    function size(inputArr){
        let stack1 = [];
        let stack2 = [];
    
        let validMark = (m) => {
            return ['+', '-', '*', '/', '(', ')'].includes(m);
        }
    
        let compareDict = {
            '+': 0,
            '-': 0,
            '*': 1,
            '/': 1
        }
    
        let compare = (a, b) => {
            if(!compareDict.hasOwnProperty(a) || !compareDict.hasOwnProperty(b)){
                throw `错误符号 a=${a} b=${b}`;
            }
            return compareDict[a] < compareDict[b];
        }
    
        //中缀转后缀
        //数字直接入栈2,符号如果优先级低于栈顶,依次出栈1,入栈2.
        for (const e of inputArr) {
            let num = Number.parseInt(e);
            //数字直接入栈2
            if(!Number.isNaN(num)){
                stack2.push(num);
            }else{//非数字,2种情况,可能是括号,可能是非括号
                if(!validMark(e)){
                    throw `非法符号${e}`;
                }
                if(e == '('){//左括号直接入栈1
                    stack1.push(e);
                }else if(e == ')'){//右括号依次出栈
                    let mark = null;
                    let valid = false;
                    while(stack1.length > 0 && mark != '('){//复杂度O(1)
                        mark = stack1.pop();
                        if(mark != '('){
                            stack2.push(mark);
                        }else{
                            valid = true;
                        }
                    }
                    if(!valid){
                        throw '非法表达式';
                    }
                }else{//如果是正常运算符,则依次与栈1顶元素比较,若栈顶元素优先级高,则出栈
                    let mark = null;
                    let handled = false;
                    while(stack1.length > 0 && mark != '('){//复杂度O(1)
                        mark = stack1.pop();
                        //只有栈顶元素优先级 < e时才结束循环,否则栈顶元素优先级 >= e时,就不停出栈
                        if(mark == '(' || compare(mark, e)){
                            stack1.push(mark);
                            stack1.push(e);
                            handled = true;
                            break;
                        }else{
                            stack2.push(mark);
                        }
                    }
                    //所有符号都出栈了或者遇到了'('
                    if(!handled){
                        stack1.push(e);
                    }
                }
            }
        }
    
        while(stack1.length > 0){
            stack2.push(stack1.pop());
        }
    
        if(stack1.length != 0){
            throw '表达式错误';
        }
    
        while(stack2.length > 0){//O(n)
            let mark = stack2.shift();
            let num = Number.parseInt(mark);
            if(!Number.isNaN(num)){//数字
                stack1.push(num);
            }else{//符号
                let n1 = stack1.pop();
                let n2 = stack1.pop();
                if(!n1 || !n2){
                    throw '表达式错误';
                }
                let v = eval(`${n2}${mark}${n1}`);
                stack1.push(v);
            }
        }
        console.log(`stack1=${stack1}`);
        return stack1.pop();
    }
    

    4.0 复杂度分析

    上述2个过程复杂度都比较低,如果遇到数字都是直接保存,遇到符号会进行一次比较或计算,时间复杂度为O(n)。
    上述算法中使用了3个栈或队列,其中2个栈能够重复使用,空间复杂度为O(2n)。

    5.0 注意

    上面的算法只是关注算法本身逻辑,健壮性及通用性比较差,如果用于正式的程序,还需要增加很多验证逻辑及更多运算符的支持。

    相关文章

      网友评论

        本文标题:算法(3)简单四则运算

        本文链接:https://www.haomeiwen.com/subject/elxuiqtx.html