【算法】表達式求值--逆波蘭算法介紹


逆波蘭算法介紹

假定給定一個只 包含 加、減、乘、除,和括號的算術表達式,你怎么編寫程序計算出其結果?

問題是:在表達式中,括號,以及括號的多層嵌套 的使用,運算符的優先級不同等因素,使得一個算術表達式在計算時,運算順序往往因表達式的內容而定,不具規律性。 這樣很難編寫出統一的計算指令。
使用逆波蘭算法可以輕松解決這個問題。他的核心思想是將普通的中綴表達式轉換為后綴表達式。

什么是中綴表達式?例如a+b,運算符在兩個操作數的中間。這是我們從小學開始學習數學就一直使用的表達式形式。

什么是后綴表達式?例如a b + ,運算符在兩個操作數的后面。后綴表達式雖然看起來奇怪,不利於人閱讀,但利於計算機處理。

轉換為后綴表達式的好處是:
1、去除原來表達式中的括號,因為括號只指示運算順序,不是實際參與計算的元素。
2、使得運算順序有規律可尋,計算機能編寫出代碼完成計算。

 

算術表達式的組成部分

一個表達式有如下及部分元素組成

  • 操作數:可以是任何數值:1,89 , 3.14159 ...
  • 運算符:

    分為單目運算符 如 sin , cos , 雙目運算符 如 加、減、乘、除 。

    運算符還會分為左結合性和右結合性。同級的左結合性的運算符從左往右依次計算。同級的右結合性的運算符從右往左依次計算。
    如: 7-6+1 等價於 (7-6) + 1 ,因為普通加減運算符是左結合性的。
    如:C語言中的組合賦值語句: a = b = 1 等價於 a = (b=1) ,因為賦值運算符在C中是右結合性的。

    對於單目運算符,還分為前綴運算符和后綴運算符,如 sin(x) 是前綴運算符,而 階乘運算符 : n ! 就是后綴運算符。

 

  • 分界符:一般是圓括號 ( ) , 用於指示運算的先后順序。

在本文中,只會考慮算術表達式 有 加、減、乘、除 運算符, 左右圓括號 ( , ) ,以及合法的數字簡單的情況。對於更加復雜的運算符,只要對這個算法輕微修改,就可以支持。

 

逆波蘭算法的原理

逆波蘭算法的核心步驟就2個:
1、將中綴表達式轉換為后綴表達式,例如輸入的原始表達式是 3*(5+7) ,轉換得到 3 5 7 + *
2、根據后綴表達式,按照特定的計算規則得到最終計算結果

下面詳細介紹這個2步的操作。

中綴表達式轉換為后綴表達式
你需要設定一個棧SOP,和一個線性表 L 。SOP用於臨時存儲運算符和左括號分界符( ,L用於存儲后綴表達式。
遍歷原始表達式中的每一個表達式元素
(1)如果是操作數,則直接追加到 L中。只有 運算符 或者 分界符( 才可以存放到 棧SOP中
(2)如果是分界符
         Ⅰ 如果是左括號 ( , 則 直接壓入SOP,等待下一個最近的 右括號 與之配對。
          Ⅱ 如果是右括號),則說明有一對括號已經配對(在表達式輸入無誤的情況下)。不將它壓棧,丟棄它,然后從SOP中出棧,得到元素e,將e依次追加到L里。一直循環,直到出棧元素e 是 左括號 ( ,同樣丟棄他。
(3)如果是運算符(用op1表示)
        Ⅰ如果SOP棧頂元素(用op2表示) 不是運算符,則二者沒有可比性,則直接將此運算符op1壓棧。 例如棧頂是左括號 ( ,或者棧為空。
         Ⅱ 如果SOP棧頂元素(用op2表示) 是運算符 ,則比較op1和 op2的優先級。如果op1 > op2 ,則直接將此運算符op1壓棧。
如果不滿足op1 > op2,則將op2出棧,並追加到L,重復步驟3。
也就是說,如果在SOP棧中,有2個相鄰的元素都是運算符,則他們必須滿足:下層運算符的優先級一定小於上層元素的優先級,才能相鄰。

最后,如果SOP中還有元素,則依次彈出追加到L后,就得到了后綴表達式。

 

偽代碼

#將參數中綴表達式expression轉為后綴表達式存放在L中,返回L
function infixToSuffix(expression):
{
    for each element in expression        #對表達式中的每一個元素
    {
        if (element 是一個操作數)
        {
            L.append(element)             #將這個元素追加到線性表L后
        }

        else if (element 是 一個運算符)
        {
            While (sop棧 不為空  &&  sop棧頂元素 是一個運算符  && element的優先級 <= sop棧頂運算符元素的優先級 )
            {
                L.append(sop.pop())
            }
            sop.push(element);     
        }

        else if(element 是 一個分界符)
        {
            if (element is  '('  )
            {
                sop.push(element)
            }
            else if( element is  ')'  )
            {
                While (sop棧不為空 &&   sop棧頂元素 不是 '('  )    #將匹配的2個括號之間的棧元素彈出,追加到L
                {
                    L.append( sop.pop() ); 
                }
                if(sop棧不為空 )
                {
                    sop.pop()           #將匹配到的 '(' 彈出丟棄
                }
            }
        }

    }

    While (sop 棧 不為空)       #將sop棧中剩余的所有元素彈出,追加到L后
    {
        L.append(sop.pop())
    }

    return L
}   

 

示例圖

 

 

根據后綴表達式計算得到最終結果
 
執行這步操作時,也需要一個棧scalc,用於存放計算中的操作數。

偽代碼

function suffixToResult(suffix_expression)
{
    for each element in suffix_expression
    {
        if(element 是 操作數)
        {
            scalc.push(element)
        }
        else if(element 是運算符)
        {
            #從棧中彈出2個操作數 num1 和 num2 。注意:后彈出的num2是第一操作數,num1是第二操作數 。 #因為這里考慮的都是二元運算符,因此需要彈2個元素出來進行運算。
            num1 = scalc.pop()
            num2 = scalc.pop()

            #使用element代表的運算符完成 num2 和 num1 的計算,產生臨時結果 temp_value
            temp_value = num2 【element的運算符: +  ,-  ,* , / 】 num1

            #將temp_value壓棧
            scalc.push(temp_value)   
        }

        #如果一切正常,最后棧scalc中僅僅只有一個元素,這個元素就是最終的結果 return sclac.pop()
    }
}

 

示例圖

 

代碼實現(Java)

1、核心算法實現是 InfixExpression類中的   public SuffixExpression toSuffixExpression()  和 SuffixExpression  類中的  double getResultValue() throws Exception
2、並沒有實現解析輸入的代碼,因此算術表達式只能通過硬編碼構造。涉及到編譯原理的知識,書到用時方恨少,哎。
3、所有的操作數內部使用double存儲。

 

 

package com. lulix86 .calc ;

import java. util .ArrayList ;
import java. util .Deque ;
import java. util .HashMap ;
import java. util .Iterator ;
import java. util .LinkedList ;
import java. util .List ;
import java. util .Map ;
import java. util .regex . Matcher;
import java. util .regex . Pattern;

public class App
{

      public static void main (String [] args )
      {

            InfixExpression expression = new InfixExpression () ;

            //   (1+8)-(3*(4-1))  = 0

            expression .append ( "(") ;
            expression .append ( "1") ;
            expression .append ( "+") ;
            expression .append ( "8") ;
            expression .append ( ")") ;
            expression .append ( "-") ;
            expression .append ( "(") ;
            expression .append ( "3") ;
            expression .append ( "*") ;
            expression .append ( "(") ;
            expression .append ( "4") ;
            expression .append ( "-") ;
            expression .append ( "1") ;
            expression .append ( ")") ;
            expression .append ( ")") ;


            System . out. println( "原始表達式:" + expression );

            try
            {
                 System . out. println( "計算結果是:" + expression. getResultValue () ) ;
            } catch (Exception e)
            {

                 System . out. println( "一定是表達式輸入的有問題,請檢查后重試" ) ;
            }


      }


}

/**
 * 表達式元素的公父類。表達式元素具體分為:運算符類,操作數類,分界符類。因此這個類有3個子類。 這是一個抽象類。 這個類的作用如下:
 * 1、定義了表達式元素的共同屬性:String content,用來存儲元素的字符串形式的內容。
 * 2、限定了分界符元素和運算符元素的相關性質:如可取的content值,運算符的優先級等。
 * 2、這個類提供了一些供子類使用的工具函數:isLegalXXXX。
 *
 *
 * @see ExpressionDelimeter
 * @see ExpressionOperator
 * @see ExpressionOperand
 *
 */
abstract class ExpressionElement
{

      // -----------------------------分界符-----------------------------------

      // 表達式中的分界符:左右圓括號
      public static final String LEFT_PARENTHESES = "(" ;
      public static final String RIGHT_PARENTHESES = ")" ;

      protected static boolean isLegalDelimeter (String content)
      {
            if ( LEFT_PARENTHESES .equals ( content) || RIGHT_PARENTHESES . equals( content ))
                 return true ;
            return false ;
      }

      // -----------------------------運算符-----------------------------------
      // 運算符:這里只用到了常見的這4個運算符
      public static final String PLUS = "+" ;
      public static final String MINUS = "-" ;
      public static final String MULTIPLE = "*" ;
      public static final String DIVIDE = "/" ;

      // 將運算符 和 他的 優先級 通過 k-v 對存放到 Map中:運算符的優先級分為2個等級,用數字1 和2代表,數值越大,優先級越高
      protected static final Map <String , Integer> operatorPiority = new HashMap <> ();
      static
      {
            operatorPiority .put ( PLUS, 1) ;
            operatorPiority .put ( MINUS, 1) ;
            operatorPiority .put ( MULTIPLE, 2) ;
            operatorPiority .put ( DIVIDE, 2) ;
      }

      protected static boolean isLegalOperator (String content)
      {
            if ( ! operatorPiority. containsKey (content ))
                 return false ;
            return true ;
      }

      // -----------------------------操作數-----------------------------------
      // 通過正則表達式校驗一個字符串是否是合法的數字 。
      protected static boolean isLegalOperand (String content)
      {
            Pattern numberPat = Pattern .compile ( "(\\+|-)?(\\d+\\.)?\\d+" );
            Matcher mat = numberPat .matcher ( content) ;
            if ( ! mat. matches ())
                 return false ;

            return true ;
      }

      protected final String content ;

      protected ExpressionElement ( String content )
      {
            this .content = content ;
      }

      public String getContent()
      {
            return content ;
      }

      @Override
      public String toString()
      {
            return content . toString() ;
      }

}

/**
 * 表達式運算符類。 構造函數私有,不可以自己創建實例,只能用類中預定義的4個靜態類對象,分別代表了加減乘除4個運算符。 類對象不可變。
 * 實現了Comparable接口,compareTo方法用來比較2個運算符的優先級。
 *
 */

class ExpressionOperator extends ExpressionElement implements Comparable <ExpressionOperator >
{

      public static final ExpressionOperator OP_MINUS = new ExpressionOperator (ExpressionElement . MINUS) ;
      public static final ExpressionOperator OP_PLUS = new ExpressionOperator (ExpressionElement . PLUS) ;
      public static final ExpressionOperator OP_MULTIPLE = new ExpressionOperator (ExpressionElement . MULTIPLE) ;
      public static final ExpressionOperator OP_DEVIDE = new ExpressionOperator (ExpressionElement . DIVIDE) ;

      // -----------------------------------------------------

      private final int priority ; // 運算符的優先級

      // 不可以在類外實例化運算符,不允許創建其它的新的運算符
      private ExpressionOperator ( String content )
      {
            super (content ) ;

            if ( ! isLegalOperator( content ))
                 throw new IllegalArgumentException( "運算符 " + content + " 不是合法的" );

            this .priority = operatorPiority .get ( content) ;
      }

      public int getPriority()
      {
            return priority ;
      }

      @Override
      public int compareTo( ExpressionOperator other )
      {
            return this . priority - other . priority;
      }

      @Override
      public boolean equals( Object obj )
      {
            if ( obj == null)
                 return false ;
            if ( obj == this)
                 return true ;
            if ( obj .getClass () != this. getClass ())
                 return false ;

            ExpressionOperator other = ( ExpressionOperator) obj;

            return this . getContent() . equals( other .getContent ()) ;
      }

}

/**
 * 表達式操作數類。
 *
 * 使用double作為數字的存儲類型。 不可變類型。 實現了Comparable接口,compareTo方法用來比較2個操作數的大小。
 *
 * 因為操作數是不可枚舉的,因此這個類可以創建實例。
 */
class ExpressionOperand extends ExpressionElement implements Comparable <ExpressionOperand >
{

      private final double value ;

      public ExpressionOperand ( String content )
      {
            super (content ) ;
            try
            {
                 value = Double. parseDouble (content ) ;
            } catch (NumberFormatException e)
            {
                 throw new IllegalArgumentException( content + " 不是一個合法的數字" );
            }
      }

      public ExpressionOperand ( double value )
      {
            super (Double . toString( value ));
            this .value = value ;
      }

      public double getValue()
      {
            return value ;
      }

      @Override
      public int compareTo( ExpressionOperand other )
      {
            return Double . compare( this .value , other . value) ;
      }

      @Override
      public String toString()
      {
            return Double . toString( this .value ) ;
      }
}

/**
 * 表達式分界符類。 不可變類型。 構造函數私有,不可以自己創建實例,只能用類中預定義的2個靜態類對象,分別代表左,右括號。
 *
 */
class ExpressionDelimeter extends ExpressionElement
{

      public static final ExpressionDelimeter DM_LEFT_PARENTHESES = new ExpressionDelimeter (
                 ExpressionElement . LEFT_PARENTHESES) ;
      public static final ExpressionDelimeter DM_RIGHT_PARENTHESES = new ExpressionDelimeter (
                 ExpressionElement . RIGHT_PARENTHESES) ;

      private ExpressionDelimeter ( String content )
      {
            super (content ) ;
            if ( ! isLegalDelimeter( content ))
                 throw new IllegalArgumentException( "分界符 " + content + " 不是合法的" );
      }

      @Override
      public boolean equals( Object obj )
      {
            if ( obj == null)
                 return false ;
            if ( obj == this)
                 return true ;
            if ( obj .getClass () != this. getClass ())
                 return false ;

            ExpressionDelimeter other = ( ExpressionDelimeter) obj ;

            return content . equals( other .content ) ;
      }

}

/**
 * 表達式類。 你可以把這個類看做是一個存儲表達式元素的線性表,因此可以使用appendXXX方法來構造一個表達式。
 *
 * 封裝了工具函數 infixToSuffix,用於將中綴表達式轉換為后綴表達式。
 * 實現了 Iterable接口,可以迭代ExpressionElement元素對象。
 */

abstract class Expression implements Iterable< ExpressionElement >
{

      // ---------------------------------------------------------------------------

      // 使用ArrayList存儲表達式元素
      protected final List< ExpressionElement > expression = new ArrayList <>() ;

      // 追加一個表達式元素對象,不可以追加空元素。
      public boolean append( ExpressionElement e )
      {
            if ( e == null)
                 return false ;
            expression .add ( e) ;
            return true ;
      }

      public boolean append( String content )
      {
            switch ( content )
            {
            case ExpressionElement . LEFT_PARENTHESES:
                 expression .add ( ExpressionDelimeter. DM_LEFT_PARENTHESES ) ;
                 break ;
            case ExpressionElement . RIGHT_PARENTHESES:
                 expression .add ( ExpressionDelimeter. DM_RIGHT_PARENTHESES ) ;
                 break ;
            case ExpressionElement . PLUS:
                 expression .add ( ExpressionOperator. OP_PLUS );
                 break ;
            case ExpressionElement . MINUS:
                 expression .add ( ExpressionOperator. OP_MINUS );
                 break ;
            case ExpressionElement . MULTIPLE:
                 expression .add ( ExpressionOperator. OP_MULTIPLE );
                 break ;
            case ExpressionElement . DIVIDE:
                 expression .add ( ExpressionOperator. OP_DEVIDE );
                 break ;
            default :
                 try
                 {
                      ExpressionOperand operand = new ExpressionOperand (content ) ; // 構造時使用了parseDouble
                      expression .add ( operand) ;

                 } catch (Exception e)
                 {
                      return false ;
                 }

            }
            return true ;
      }

      @Override
      public String toString()
      {
            boolean firstAdd = true ;
            StringBuilder sb = new StringBuilder () ;
            for ( ExpressionElement e : expression )
            {
                 if ( ! firstAdd)
                 {
                      sb .append ( " ") ;
                 } else
                 {
                      firstAdd = false;
                 }
                 sb .append ( e. toString ());
            }
            return sb . toString() ;
      }

      @Override
      public Iterator < ExpressionElement> iterator()
      {
            return expression . iterator() ;
      }

      public void clear()
      {
            this .expression . clear() ;
      }

      // 獲取表達式最終的運算的結果
      public abstract double getResultValue() throws Exception ;

}

class SuffixExpression extends Expression
{

      private double doPlus( ExpressionOperand a , ExpressionOperand b )
      {
            return a . getValue() + b .getValue () ;
      }

      private double doMinus( ExpressionOperand a , ExpressionOperand b )
      {
            return a . getValue() - b .getValue () ;
      }

      private double doMultiple( ExpressionOperand a , ExpressionOperand b )
      {
            return a . getValue() * b .getValue () ;
      }

      private double doDevide( ExpressionOperand a , ExpressionOperand b )
      {
            return a . getValue() / b .getValue () ;
      }

      // SuffixExpression 本身已經是一個后綴表達了。getResultValue計算出結果就OK了
      @Override
      public double getResultValue () throws Exception
      {
            SimpleStack <ExpressionOperand > scalc = new SimpleStack <>() ;

            for ( ExpressionElement e : expression )
            {

                 if ( e instanceof ExpressionOperand)
                 {
                      scalc .push (( ExpressionOperand) e) ;
                 } else if ( e instanceof ExpressionOperator )
                 {

                      ExpressionOperator operator = ( ExpressionOperator) e; // 獲取這個運算符
                      ExpressionOperand opf = scalc .pop () ; // 彈出二元運算符的第二個操作數
                      ExpressionOperand ops = scalc .pop () ; // 彈出二元運算符的第一個操作數
                      ExpressionOperand temp = null ; // 存儲臨時運算結果

                      if ( opf == null || ops == null )
                            throw new Exception( "表達式不合法,不能完成計算" ) ;

                      if ( operator. equals (ExpressionOperator . OP_PLUS))
                      {
                            temp = new ExpressionOperand (doPlus ( ops, opf)) ;
                      } else if ( operator. equals (ExpressionOperator . OP_MINUS))
                      {
                            temp = new ExpressionOperand (doMinus ( ops, opf)) ;
                      } else if ( operator. equals (ExpressionOperator . OP_MULTIPLE))
                      {
                            temp = new ExpressionOperand (doMultiple ( ops, opf)) ;
                      } else if ( operator. equals (ExpressionOperator . OP_DEVIDE))
                      {
                            temp = new ExpressionOperand (doDevide ( ops, opf)) ;
                      }

                      scalc .push ( temp) ;

                 } // else if

            } // end foreach

            if ( scalc .size () != 1)
                 throw new Exception( "表達式不合法,不能完成計算" ) ; // 從 scalc棧中取出最后一個元素就是結果

            return scalc . pop() . getValue() ;
      }

}

class InfixExpression extends Expression
{
      public SuffixExpression toSuffixExpression()
      {
            SuffixExpression suffix = new SuffixExpression () ; // suffix是一個用來存儲表達式元素的線性表對象,對應算法中的L
            SimpleStack <ExpressionElement > sop = new SimpleStack <>() ; // sop 棧

            // 遍歷原始表達式中的每一個元素
            for ( ExpressionElement e : expression )
            {
                 if ( e instanceof ExpressionOperand) // 如果是操作數,則直接追加到后綴表達式suffix中
                 {
                      suffix .append ( e) ;
                 } else if ( e instanceof ExpressionDelimeter ) // 如果是分界符
                 {
                      if ( e. equals (ExpressionDelimeter . DM_LEFT_PARENTHESES )) // 是 左括號,則直接壓棧
                      {
                            sop .push ( e) ;
                      } else if ( e. equals (ExpressionDelimeter . DM_RIGHT_PARENTHESES )) // 是 右括號,則從 sop中彈出與這個括號配對的中間的所有元素,
                      { // 並追加到后綴表達式中
                            while ( ! sop. isEmpty () && ! sop. peek (). equals (ExpressionDelimeter . DM_LEFT_PARENTHESES ))
                            {
                                 suffix .append ( sop. pop ()); // 將元素出棧,追加到 suffix 表中去
                            }

                            if ( ! sop. isEmpty ())
                            {
                                 sop .pop () ; // 將棧頂的( 出棧,丟棄。
                            }
                      }

                 } else if ( e instanceof ExpressionOperator ) // 如果是運算符
                 {

                      while ( ! sop. isEmpty () && sop . peek() instanceof ExpressionOperator
                                 && 0 >= (( ExpressionOperator) e ). compareTo ((ExpressionOperator ) sop . peek()))
                      {
                            suffix .append ( sop. pop ());
                      }
                      sop .push ( e) ;

                 }
            } // end of foreach

            // 將 sop棧中剩余的元素全部追加到suffix后
            while ( ! sop. isEmpty ())
            {
                 suffix .append ( sop. pop ());
            }

            return suffix ;
      }

      @Override
      public double getResultValue () throws Exception
      {
            return toSuffixExpression () .getResultValue () ;
      }

}

/**
 *
 * 因為Java集合框架中的Stack類是線程安全的, 但是這里不需要這種特性,為了提高效率,使用雙端隊列作為內部實現,自己封裝成為一個棧數據結構。
 *
 * @see java.util.Stack <E>
 * @see java.util.Deque <E>
 * @see java.util.LinkedList <E>
 */
class SimpleStack < E>
{

      private final Deque< E > deque = new LinkedList < E> ();

      public E pop()
      {
            return deque . pop() ; // 雙端隊列的第一個元素,也就是棧的棧頂
      }

      public E peek()
      {
            return deque . peek() ;
      }

      public void push( E e )
      {
            deque .push ( e) ; // 壓棧
      }

      public int size()
      {
            return deque . size() ;
      }

      public boolean isEmpty()
      {
            return 0 == deque .size () ;
      }

      @Override
      public String toString()
      {
            StringBuilder sb = new StringBuilder () ;
            sb .append ( "棧頂[") ;
            for ( E e : deque)
            {
                 sb .append ( e. toString () + "," ) ;
            }

            sb .append ( "]棧底") ;

            return sb. toString ();
      }

}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM