1. 程式人生 > >表示式求值演算法總結

表示式求值演算法總結

表示式求值演算法

表示式求值,一般採用棧和佇列的方式來求值,下面介紹表示式求值的兩種演算法。

方法一、使用兩個棧,一個為操作符棧OPTR(operator),一個是運算元棧OPND(operand)
演算法過程:
當輸入 3 * ( 4 - 1 * 2 ) + 6 / ( 1 + 1 )時,為簡單方便,我們輸入時,按照字元的順序一個一個的處理,比如

ch = getchar()

然後根據ch 的值判斷。

  • ch 是數字,直接壓入運算元棧OPND
  • ch'(',直接入棧OPTR;若 ch')',若OPTROPND 非空,彈出OPTR的棧頂操作符,彈出OPND
    棧頂的兩個運算元,做運算,然後見個結果壓入棧OPND,直到彈出的OPTR棧頂元素時')';
  • ch 是操作符(比如+, -, *, /),如果OPTR棧頂元素是 (,直接入棧OPTR,如果不是'('OPTR棧非空且棧頂元素操作符的優先順序大於ch,那麼彈出OPTR的棧頂操作符,並彈出OPND中棧頂的兩個元素,做運算,將運算結果入棧OPND,此時,重複這一步操作;否則將ch入棧OPTR
  • ch為EOF,說明表示式已經輸入完成,判斷OPTR是否為空,若非空,一次彈出OPTR棧頂操作符,並與OPND棧頂兩個元素做運算,將運算結果入棧OPND,最後表示式的結果即OPND的棧底元素。

以表示式3 * ( 4 - 1 * 2 ) + 6 / ( 1 + 1 )

為例,計算過程如下所示:

OPTR OPND ch 備註
3 3
* 3 *
*,( 3 (
*,( 3,4 4
*,(,- 3,4 -
*,(,- 3,4,1 1
*,(,-,* 3,4,1 *
*,(,-,* 3,4,1,2 2
*,(,- 3,4,2 ) OPND彈出2和1,OPTR彈出*,計算結果入棧OPND
*,( 3,2 ) OPND彈出2和4,OPTR彈出-,計算結果入棧OPND
* 3,2 ) OPTR棧頂彈出的是)
+ 6 + OPTR棧頂元素優先順序大於ch,將OPND棧的3和2與OPTR的*運算,結果入棧OPND,ch入棧OPTR
+ 6,6 6
+,/ 6,6 /
+,/,( 6,6 (
+,/,( 6,6,1 1
+,/,(,+ 6,6,1 +
+,/,(,+ 6,6,1,1 1
+,/,( 6,6,2 ) OPND的1和1,與OPTR的+運算,結果入棧OPND
+,/ 6,6,2 )
+ 6,3 表示式已經輸入完成,OPTR非空,繼續計算。OPND的2和6,OPTR的/運算
9 計算結果

通過上述的計算過程,寫出虛擬碼如下所示:

void GetExpress(Stack * OPTR, Stack * OPND)
{
    char ch;
    while ((ch = getchar ()) != EOF) {
        if (IsDigit (ch)) {
            PushStack (OPND, ch);
        }
        else if (ch == '(')
            PushStack (OPTR, ch);
        else if (ch == ')') {
            while (!IsStackEmpty(OPTR)) {
                PopStack (OPTR, op);
                if (op == ')')
                    break;
                PopStack (OPND, num2);
                PopStack (OPND, num1);
                res = Calc (num1, num2, op);
                PushStack (OPND, res);
            }
        }
        else if (ch == '+' || ch == '-'
            || ch == '*' || ch == '/') {
            while (!IsStackEmpty (OPTR) && GetTop (OPTR)!='(' && GetTop (OPTR)>ch) {
                PopStack (OPTR, op);
                PopStack (OPND, num2);
                PopStack (OPND, num1);
                res = Calc (num1, num2, op);
                PushStack (OPND, res);
            }
            if (IsStackEmpty (OPTR) || GetTop(OPTR)=='(')
                PushStack (OPTR, ch);
        }
    }
}
// 當表示式輸入完成後,需要對OPTR棧和OPND中的元素進行運算
int GetValue(Stack * OPTR, Stack * OPND)
{
    while (!IsStackEmpty (OPTR)) {
        PopStack (OPTR, op);
        PopStack (OPND, num2);
        PopStack (OPND, num1);
        res = Calc (num1, num2, op);
        PushStack (OPND, res);
    }
    // 最後的運算元棧OPND棧頂元素即是表示式的值
    return GetTop(OPND);
}

PS: 上面沒有指出表示式非法的情況

方法二:採用中綴表示式的方法,求取表示式的中綴表示式,借用一個操作符棧OPTR和中綴表示式佇列Queue,求取中綴表示式,然後對中綴表示式求值。

求取中綴表示式

  • ch 是數字,直接入佇列Queue;
  • ch 是操作符(+, -, *, /),如果OPTR棧頂元素是(或者OPTR 為空,直接入棧OPTR;若OPTR非空,且棧頂元素的優先順序大於ch,先出棧,且將出棧元素入隊Queue,直到棧頂元素小於ch,然後將ch入棧;否則直接將ch入棧;
  • ch(,直接入棧 OPTR;
  • ch),出棧併入佇列,直到出棧元素是 (,若棧為空且沒有(出現,說明表示式非法。
  • 當表示式輸入完成時,判斷棧是否為空,若非空,依次彈棧併入佇列

求取中綴表示式的值,需要借用一個棧

  • 若佇列非空,出隊q_ele;如果出隊元素q_ele是數字,入棧S;否則取出棧頂兩個元素,與q_ele這個操作符左運算,運算結果入棧S
  • 最後的結果為棧頂元素

仍以表示式3 * ( 4 - 1 * 2 ) + 6 / ( 1 + 1 )為例,計算過程如下:

計算中綴表示式:

OPTR Queue ch 備註
3 3
* 3 * 棧為空,直接入棧
*,( 3 (
*,( 3,4 4
*,(,- 3,4 - 棧頂是’(‘
*,(,- 3,4,1 1
*,(,-,* 3,4,1 * 棧頂元素-優先順序小於*,直接入棧
*,(,-,* 3,4,1,2 2
* 3,4,1,2,*,- ) 依次出棧併入隊,知道出棧元素是’)’
+ 3,4,1,2,*,-,* + 棧頂元素*優先順序大於+,出棧入隊,然後+入棧
+ 3,4,1,2,*,-,*,6 6
+,/ 3,4,1,2,*,-,*,6 /
+,/,( 3,4,1,2,*,-,*,6 (
+,/,( 3,4,1,2,*,-,*,6,1 1
+,/,(,+ 3,4,1,2,*,-,*,6,1 +
+,/,(,+ 3,4,1,2,*,-,*,6,1,1 1
+,/ 3,4,1,2,*,-,*,6,1,1,+ ) 依次出棧併入隊,知道出棧元素是’)’
3,4,1,2,*,-,*,6,1,1,+,/,+ 表示式已經輸入完成,將棧依次出棧併入隊

此時中綴表示式為3,4,1,2,*,-,*,6,1,1,+,/,+,佇列左邊是頭,右邊是尾。

計算中綴表示式的值:

Queue S q_ele 備註
3,4,1,2,*,-,*,6,1,1,+,/,+
4,1,2,*,-,*,6,1,1,+,/,+ 3 3
1,2,*,-,*,6,1,1,+,/,+ 3,4 4
2,*,-,*,6,1,1,+,/,+ 3,4,1 1
*,-,*,6,1,1,+,/,+ 3,4,1,2 2
-,*,6,1,1,+,/,+ 3,4,2 * 將棧頂2和1,對操作符*做運算,並將結果入棧
*,6,1,1,+,/,+ 3,2 - 將棧頂2和4,對操作符-做運算,並將結果入棧
6,1,1,+,/,+ 6 * 將棧頂2和3,對操作符*做運算,並將結果入棧
1,1,+,/,+ 6,6 6
1,+,/,+ 6,6,1 1
+,/,+ 6,6,1,1 1
/,+ 6,6,2 + 1+1=2
+ 6,3 / 6/2=3
9 + 6+3=9,計算結果

根據以上操作過程,寫出虛擬碼:

void GetPostExpress (Stack * OPTR, Queue * Q)
{
    while ((ch = getchar ()) != EOF) {
        if (IsDigit (ch))   //判斷是否是數字
            PushQueue (Q, ch);
        else if (ch == '(')
            PushStack (OPTR, ch);
        else if (ch == '+' || ch == '-'
            || ch == '*' || ch == '/') {
            if (IsStackEmpty (OPTR) || GetTop (OPTR)=='(')
                PushStack (OPTR, ch);

            while (!IsStackEmpty (OPTR) && GetTop (OPTR)!='('
                && GetTop (OPTR)>ch) {
                PopStack (OPTR, op);
                PushQueue (Q, op);
            }
            PushStack (OPTR, ch);
        }

        if (ch == ')') {
            while (!IsStackEmpty (OPTR)) {
                PopStack (OPTR, op);
                if (op == ')')
                    break;

                PushQueue (Q, op);
            }
        }
    }
    // 表示式輸入完成 
    while (!IsStackEmpty (OPTR)) {
        PopStack (OPTR, op);
        PushQueue (Q, op);
    }
}

// 計算中綴表示式的值
int GetValue (Queue * Q, Stack * S)
{
    while (!IsQueueEmpty (Q)) {
        PopQueue (Q, ch);
        if (IsDigit (ch))
            PushStack (S, ch);
        else {
            PopStack (S, num2); //需要判斷棧是否為空
            PopStack (S, num1);
            res = Calc (num1, num2, ch);
            PushStack (S, res);
        }
    }
    return GetTop (S);  //棧頂元素就是表示式的結果
}