zoukankan      html  css  js  c++  java
  • (六)栈的规则及应用

    目标

    1) 描述ADT栈的操作

    2) 使用栈来判定代数表达式中分隔符是否正确配对

    3) 使用栈将中缀表达式转变为后缀表达式

    4) 使用栈计算后缀表达式的值

    5) 使用栈计算中缀表达式的值

    6) 使用程序中的栈

    7) 描述Java运行时环境如何使用栈来跟踪方法的执行过程

     

    5.1 ADT栈的规格说明

      栈顶(top),栈顶项(top entry),入栈(push),出栈(pop),查看栈顶项(peek),一般地,不能在栈中查找某个具体的项。

    抽象数据类型:栈

    +push(newEntry : T) : void

    +pop() : T

    +peek() : T

    +isEmpty() : boolean

    +clear() : void

    设计决策:当栈空时pop和peek应该做什么?

    • 假定ADT不空。即增加一个能保证这个假设的前置条件pop,peek是public,不能信任客户遵从这些方法所需的任何前置条件)
    • 返回null(具有二义性:ADT空 or 元素是null,需要客户调用第二个方法来解释另一个方法的动作)
    • 抛出一个异常(此情况下,认为返回null是有效数据)

    设计决策:当栈为空时,pop和peek应该抛出哪异常:受检异常还是运行时异常?

      如果方法的客户能在执行时从异常中合理地恢复,它就应该抛出受检异常。这种情况下,客户可以直接处理异常,或者将它传播到另一个方法中。如果将异常看作对你方法的不正常使用(即使用你方法的程序员的错误),则方法应该抛出运行时异常。运行时异常不需要(但可以)throws子句中说明,而且也不需要(但可以)被客户捕获。

      这里将栈为空时调用pop和peek方法看作客户的错误,所以抛出运行时异常。

    安全说明:信任

      你能信任一段代码吗?不能,除非能证明它的动作是正确且安全的,这种情形下它成为可信代码(trusted code)。能信任客户以确定的方式使用你的软件,所以遵从任何及所有的前置条件,并能正确解释返回码吗?不能。但是类内的私有方法确实能保证或信任,其前置条件要被遵从,它的返回值可被正确对待。

    安全说明:设计原则

    • 使用前置条件和后置条件来记录假设
    • 不要信任客户能正确使用共有方法
    • 避免返回值的二义性
    • 宁愿抛出异常,也不要用返回值来表示一个问题

    5.2 使用栈来处理代数表达式

      将二元运算符放在其操作数中间,如a+b,为中缀表达式;放在操作数前,如+ab,为前缀表达式(有时称波兰表示法(Polish notation),由波兰数学家Jan Lukasiewicz于19世纪20年代提出);放在操作数后,如ab+,为后缀表达式(有时称逆波兰表示法(reverse Polish notation))。

    5.2.1 问题求解:检查中缀表达式中平衡的分隔符(括号配对)

      平衡表达式(balanced expression)包含配对正确的或平衡的(balanced)分隔符。

    public class BalanceChecker {
      public static void main(String[] args) {
        String expression = "a {b [c (d + e)/2 - f] + 1}";
        boolean isBalanced = BalanceChecker.checkBalance(expression);
        if (isBalanced) {
          System.out.println(expression + " is balanced");
        }
        else {
          System.out.println(expression + " is not balanced");
        } // end if
      } // end main
    
    /**
     * Decides whether the parentheses, brackets, and braces
     * in a string occur in left/right pairs.
     * @param expression: A string to be checked.
     * @return: True if the delimiters are paired correctly.
     */
    
      public static boolean checkBalance(String expression) {
        StackInterface<Character> openDelimiterStack = new OurStsck<>();
        int characterCount = expression.length();
        boolean isBalanced = true;
        int index = 0;
        char nextCharacter = ' ';
        while (isBalanced && (index < characterCount)) {
          nextCharacter = expression.charAt(index);
          switch (nextCharacter){
            case '{':
            case '[':
            case '(':
              openDelimiterStack.push(nextCharacter);
              break;
             case '}':
                case ']':
               case ')':
               if (openDelimiterStack.isEmpty()) {
                   isBalanced = false;
               }  
               else {
                  char openCharacter = openDelimiterStack.pop();
                  isBalanced = isPaired(openCharacter, nextCharacter);
               } // end if
               break;
               default: break;  // Ignore unexpected characters
            } // end switch
            index++;
         } // end while
         if (!openDelimiterStack.isEmpty()) {
             isBalanced = false;
         } // end if
         return isBalanced;
     } // end checkBalance
    
    // Returns true if the given characters, open and close, form a pair
    // of parentheses, brackets, or braces.
        private static boolean isPaired(char open, char close)     {
          return (open == '(' && close == ')') ||
                  (open == '[' && close == ']') ||
                  (open == '{' && close == '}');
        } // end isPaired
    } // end BalanceChecker    

    5.2.2 问题求解:将中缀代数表达式转换为后缀表达式 

    最终目标是如何计算中缀表达式,但后缀表达式更容易求职值,所以先看一个中缀表达式如何表示为后缀表达式的形式。

      中缀表达式对应后缀表达式的例子:

    中缀

    后缀

    a + b

    a b +

    (a + b) * c

    a b + c *

    a + b * c

    a b c * +

    1) 手算策略

      将中缀表达式根据优先级添加括号,再转为后缀表达式。

    2) 转换算法的基础

      从左到右扫描中缀表达式,到遇到操作数时,将它放到正创建的新表达式的末尾。在中缀表达式中,操作数的顺序和其对应的后缀表达式中是一样的。当遇到运算符时,必须先保存它,直到能判定它在所属的输出表达式的位置为止。将运算符保存到栈中。一般地,至少要等到将它与下一个运算符的优先级进行比较时。如转换a+b*c,将+保存到栈中,当遇到b,不能将+拿出来,要看下一个运算符,下一个运算符为*,优先级高于+,所以保存*,输入c,*,再拿+abc*+。

    3) 具有相同优先级的连续运算符

      如果两个相连的运算符有相同的优先级,需要区分满足从左至右结合律的运算符(即+、-、*、/)及求幂,后者满足从右到左结合律。如a-b+c,遇到+时,栈中含有-,且部分后缀表达式是ab,减号运算符属于操作数a和b,所以-出栈,有ab-,+进栈,最后出栈,有ab-c+。表达式a^b^c:遇到第二个求幂运算符时,栈中含有^,目前有ab,当前运算符与栈顶有相同的优先级,但因为a^b^c的含义是a^(b^c),所以将第二个^入栈,abc^^。

    4) 圆括号

      圆括号改变了运算符优先级规则,将开圆括号入栈,一旦它出现在栈中,将开圆括号看作有最低优先级的运算符。即,后面的任何运算符都将入栈。当遇到闭圆括号时,将运算符出栈,且加到已有的后缀表达式的后面,直到弹出一个开圆括号时为止。算法继续,但不将圆括号加到后缀表达式。

    5) 中缀—后缀的转换

    Algorithm convertToPostfix(infix)
    // 将中缀表达式转换为等价的后缀表达式
    operatorStack = 一个新的空栈 postfix = 一个新的空字符串 while (infix还有待解析的字符){   nextCharacter = infix的下一个非空字符   switch (nextCharacter){   case 变量:     将nextCharacter添加到postfix的后面     break   case '^':     operatorStack.push(nextCharacter)     break   case '+': case '-': case '*': case '/':     while (!operatorStack.isEmpty() && nextCharater的优先级 <= operatorStack.peek()的优先级){       将operatorStack.peek()添加到postfix的后面       operatorStack.pop()     }     operatorStack.push(nextCharacter)     break   case '(':     operatorStack.push(nextCharater)     break   case ')': // 如果中缀表达式合法,则栈非空     topOperator = operatorStack.pop()     while (topOperator != '('){       将topOperator添加到postfix的后面       topOperator = operatorStack.pop()     }     break   default: break; // 忽略预期之外的字符   } } while (!operatorStack.isEmpty()){   topOperator = operatorStack.pop()   将topOperator添加到postfix的后面 } return postfix

    5.2.3 问题求解:计算后缀表达式的值

      遍历后缀表达式,遇见值入栈,遇见操作符,出栈两次,计算后结果入栈,直到栈中只有一个数值即表达式结果。

    Algorithm evaluatePostfix(postfix)
    // 计算后缀表达式
    
    valueStack = 一个新的空栈
    while (postfix还有待解析的字符){
      nextCharacter = postfix的下一个非空字符
      switch (nextCharacter){
        case 变量:
          valueStack.push(变量nextCharacter的值)
          break
        case '+': case '-': case '*': case '/':
          operandTwo = valueStack.pop()
          operandOne = valueStack.pop()
          result = nextCharacter中的操作作用于其操作数operandOne和operandTwo
          valueStack.push(result)
          break
        default: break  // 忽略预期之外的字符
      }
    }
    return valueStack.pop()

    5.2.4 问题求解:计算中缀表达式的值

      将前两个算法合为一个算法,使用两个栈直接计算中缀表达式的值。合并算法根据中缀表达式转换为后缀形式的算法,维护一个运算符栈。但该算法不将操作数添加到表达式的末尾,而是根据计算后缀表达式的算法,将操作数的值压入第二个栈中。

    Algorithm evaluateInfix(infix)
    // 计算中缀表达式
    
    operatorStack = 一个新的空栈
    valueStack = 一个新的空栈
    while (infix还有待解析的字符){
      nextCharacter = infix的下一个非空字符
      switch (nextCharacter){
        case 变量:
          valueStack.push(变量nextCharacter的值)
          break
        case '^':
          operatorStack.push(nextCharacter)
          break
        case '+': case '-': case '*': case '/':
          while (!operatorStack.isEmpty() && nextCharater的优先级 <= operatorStack.peek()的优先级){
            // 执行operatorStack栈顶的操作
            topOperator = operatorStack.pop()
            operandTwo = valueStack.pop()
            operandOne = valueStack.pop()
            result = nextCharacter中的操作作用于其操作数operandOne和operandTwo
             valueStack.push(result)
          }
          operatorStack.push(nextCharacter)
          break
        case '(':
          operatorStack.push(nextCharater)
          break
        case ')':   // 如果中缀表达式合法,则栈非空
          topOperator = operatorStack.pop()
          while (topOperator != '('){
            operandTwo = valueStack.pop()
            operandOne = valueStack.pop()
            result = nextCharacter中的操作作用于其操作数operandOne和operandTwo
            valueStack.push(result)
            topOperator = operatorStack.pop()
          }
          break
        default: break;  // 忽略预期之外的字符
      }
    }
    while (!operatorStack.isEmpty()){
      topOperator = operatorStack.pop()
      operandTwo = valueStack.pop()
      operandOne = valueStack.pop()
      result = nextCharacter中的操作作用于其操作数operandOne和operandTwo
      valueStack.push(result)
    }
    return valueStack.peek()

    5.3 Java类库:类Stack
      java类库含有类Stack,它实现了java.util包中的ADT栈。与我们定义的方法的不同之处已做标记。

    public T push(T item);

    public T pop();

    public T peek();

    public boolean empty();

    5.4 小结

    • ADT栈按后进先出的原则组织项。栈顶的项是最新添加进来的
    • 栈的主要操作(push, pop和peek)都仅处理栈顶。方法push将项添加到栈顶;pop删除并返回栈顶,而peak只是返回栈顶
    • 普通的代数表达式称为中缀表达式,因为每个二元运算符出现在它的两个操作数的中间。中缀表达式需要运算符优先级规则,且可使用圆括号来改变这些规则。
    • 可以使用值栈来计算后缀表达式的值
    • 可以使用两个栈(一个用于运算符,一个用于值)来计算中缀表达式的值
    • 像peek和pop这样的方法,当栈为空时必须有合理的动作。例如,它们可以返回null或者抛出一个异常

                                                                            

  • 相关阅读:
    VS2010 Extension实践(3)——实现自定义配置
    VS2010 Extension实践(2)
    WinRT开发系列之基础概念:WinRT不是……
    [VS2010 Extension]PowerExtension.GoToDefinition
    如何通过反射调用带有ref或者out的参数的方法[迁移]
    Win7硬盘安装和移动硬盘访问出错的修复办法[迁移]
    zt. Windows Mobile开发文章收藏
    WinRT开发系列之编程语言:功能和效率
    VS2010 Extension实践
    maven创建父子工程
  • 原文地址:https://www.cnblogs.com/datamining-bio/p/9637912.html
Copyright © 2011-2022 走看看