引入:

这里再分享一道算法题,比如说给定一个数学表达式,因为我们知道数学表达式中可以用任意多的匹配的小括号对来表示优先级,但是如果表达式很长的话,经常会出现这个表达式的括号不匹配的情况,我们就要设计一个算法来检验一个表达式是否所有括号都匹配,给出匹配的括号列表,如果不匹配也要列出不匹配的括号列表。


其实这个算法题还是有一定的实践意义的,比如说我们的IDE中的编辑器会自动检测方法,类的大括号是否匹配,其原理是一样的,首先排除所有的注释行,然后再有效内容中去查找匹配的大括号,如果不匹配,那么就报错,在IDE中就用红色波浪线显示出来。


分析:

如何解决这道题题目呢:

我们很快想到了可以用堆栈来解决,思路很简单,我们构造一个堆栈,这个堆栈中的每个元素都包含2部分,一是当前的括号是开括号还是闭括号,当前括号在数学表达式中的位置。 然后我们从左到右扫描这个表达式,一旦找到小括号,就和栈顶的符号比较,如果和栈顶的符号一致,那么就说明是同向括号,于是把这个新找到的括号压入栈顶,如果和栈顶的符号相反(比如当前栈顶是开括号,你刚好查到了一个闭括号),说明找到了一个匹配的括号,于是就把当前括号和栈顶括号连同他们的位置信息一起输出。 最后如果表达式开闭括号数量不对等,那么一定在扫描完表达式之后,堆栈中内容不为空,于是把这些不匹配的括号也依次输出。


考虑到表达式可长可短,不固定,所以我们在堆栈的内部用链表来实现。


代码:

首先,我们定义一个POJO,来表达堆栈中某个Symbol节点,它包含了3个信息,一个是当前符号(只可能是开小括号 (或者闭小括号) ),二是当前的符号在数学表达式字符串中的位置,3是链接到下一个Symbol节点的引用。

package com.charles.study.matchpara;
/**
 *
 * 这个类是可以用于校验给出的表达式的括号是否匹配的一个symbol节点
 * @author Administrator
 *
 */
public class SymbolNode {
                                                                                                                                                                                                 
    //这个符号,可以是 '(',可以是')'
    private char symbol;
    //这个符号所在的字符串表示的表达式的位置
    private int index;
    //下一个表示符号的节点
    private SymbolNode next;
                                                                                                                                                                                                 
                                                                                                                                                                                                 
    public SymbolNode(char symbol,int index,SymbolNode next){
        this.symbol =symbol;
        this.index = index;
        this.next = next;
    }
                                                                                                                                                                                                 
    public char getSymbol() {
        return symbol;
    }
    public void setSymbol(char symbol) {
        this.symbol = symbol;
    }
    public int getIndex() {
        return index;
    }
    public void setIndex(int index) {
        this.index = index;
    }
    public SymbolNode getNext() {
        return next;
    }
    public void setNext(SymbolNode next) {
        this.next = next;
    }
                                                                                                                                                                                                 
                                                                                                                                                                                                 
                                                                                                                                                                                                 
                                                                                                                                                                                                 
}



然后我们按照分析来构造一个堆栈数据结构,他提供了查看栈顶元素,压入栈,弹出栈顶元素等基本操作。

package com.charles.study.matchpara;
/**
 * 这个类表示了一个用于存储符号的Stack,我们通过比较栈顶来判断是否匹配
 * @author Administrator
 *
 */
public class SymbolStack {
                                                                                                                                                                         
    //指向栈顶节点
    private SymbolNode top;
                                                                                                                                                                         
                                                                                                                                                                         
    public SymbolStack(){
        top=null;
    }
                                                                                                                                                                         
    /**
     * 判断这个栈是否为空,判断依据 是这个栈顶元素是否为null
     * @return
     */
    public boolean isEmpty(){
        if(top == null)
            return true;
        return false;
    }
                                                                                                                                                                         
    /**
     * 把一个符号节点压入栈
     * @param symbolNode
     */
    public  void push (char symbol, int index){
                                                                                                                                                                             
        //如果是栈为空的,那么就把这个符号节点作为第一个节点,并且赋值给top
        if(isEmpty()){
            SymbolNode symbolNode = new SymbolNode(symbol,index,null);
            top = symbolNode;
        }
                                                                                                                                                                             
        //如果栈不为空,那么新建一个符号节点,让其的next为当前栈顶节点,并且自身为top
        else{
            SymbolNode symbolNode  = new SymbolNode(symbol,index,null);
            symbolNode.setNext(top);
            //更新当前栈顶元素为这个新的node
            top=symbolNode;
        }
                                                                                                                                                                             
    }
                                                                                                                                                                         
    /**
     * 获取栈顶的元素
     */
    public SymbolNode getTopElement(){
        return top;
    }
                                                                                                                                                                         
    /**
     * 弹出栈顶的元素,并且栈顶元素的下一个元素成为新的栈顶元素
     */
    public void pop(){
                                                                                                                                                                             
        //空栈不做任何操作
        if(top==null)
            return;
                                                                                                                                                                             
        //对于非空栈,则先把栈顶的下一个元素设置新的栈顶元素
        else{
            top=top.getNext();
        }
    }
}


实现很简单,就完全按照我们文章开始的思路分析。最后我们写一个工具类,它会扫描给定字符串,然后利用堆栈对这个字符串进行分析,给出结果:

package com.charles.study.matchpara;
/**
 * 工具类,用于操作符号栈
 * @author Administrator
 *
 */
public class SymbolStackUtil {
                                                                                                                                                   
    private SymbolStackUtil(){}
                                                                                                                                                   
    /**
     * 从一个符号表达式中构造一个SymbolStack
     * @param expression
     * @return
     */
    public static void parseExpression (String expression){
                                                                                                                                                       
        char symbol;
        SymbolStack symbolStack = new SymbolStack();
        SymbolNode currentTopElementInStack;
                                                                                                                                                       
        //遍历这个表达式,找到其中的所有的 小括号'('和')',并且压入栈
        for(int i=0;i<expression.length();i++){
                                                                                                                                                           
            //取得当前的字符
            symbol=expression.charAt(i);
            //看当前字符是否是'('或者')'
            if(symbol=='('  || symbol==')'){
                                                                                                                                                               
                //和当前的Stack最的栈顶元素比
                                                                                                                                                               
                                                                                                                                                               
                currentTopElementInStack = symbolStack.getTopElement();
                //如果栈顶元素为空,那么将当前的符号压入栈
                if (currentTopElementInStack==null){
                    symbolStack.push(symbol, i+1);
                }
                                                                                                                                                               
                //如果栈顶元素不为空,那么分2种情况:
                //如果和栈顶元素符号相同,那么就压入栈,否则就从栈顶弹出符号,然后和当前符号一起组成输出
                else if (currentTopElementInStack.getSymbol()==symbol){
                    symbolStack.push(symbol, i+1);
                }
                else{
                    //打印找到匹配的括号
                    System.out.println("找到匹配的括号为:"+"第"
                            +currentTopElementInStack.getIndex()+"个"+currentTopElementInStack.getSymbol()
                            +"匹配"+"第"+(i+1)+"个"+symbol);
                    //从栈顶删除当前符号
                    symbolStack.pop();
                }
                                                                                                                                                               
            }
        }
                                                                                                                                                       
        //当循环完了之后,如果栈中还有非空的元素,那么说明这些都是不匹配的括号了,我们依次打印,并且从栈顶将其移除
        if(!symbolStack.isEmpty()){
            System.out.println("剩下还有若干不匹配括号,依次是:");
                                                                                                                                                           
            while(!symbolStack.isEmpty()){
                System.out.println("第"+symbolStack.getTopElement().getIndex()+"个"
                                    +symbolStack.getTopElement().getSymbol());
                //删除栈顶元素
                symbolStack.pop();
            }
        }
                                                                                                                                                       
    }
                                                                                                                                                   
                                                                                                                                                   
    public static void main(String [] args){
                                                                                                                                                       
        //实验1
        String expression1 ="(a*(b+c)+d)";
        System.out.println("实验1: 被测表达式为:"+expression1);
        parseExpression (expression1);
                                                                                                                                                       
        //实验2
        String expression2= "a+b*c+d*(e-g+(ax*b)-3)+3*)*e";
        System.out.println("实验2: 被测表达式为:"+expression2);
        parseExpression (expression2);
    }
}


测试:

我们来测试下,我们运行工具类的main()方法,它会检查2个字符串表达式,一个是正确的,一个是开闭括号不匹配的,于是输出结果如下:

wKiom1LL7NWiAsrlAACUld5Rzn4458.jpg

显然是正确的。。


最后吐槽下自己,今天生病在家,用家里顶级配置玩游戏的台式机来写程序,果然很爽,同样的程序,执行速度比在公司快了10倍多不止,这爽死了。可惜不能天天在家。明天又要去公司了。