數據結構與算法 - 3.3.3.2 後綴表達式

3.3.3.2 後綴表達式

假設咱們有一個便攜計算器並須要計算一趟外出購物的花費。公式以下:
4.99 * 1.06 + 5.99 + 6.99 * 1.06 = ?
該例的典型計算順序是: 1. 將4.99和1.06相乘並存入A1 2. 而後將5.99與A1相加,再將結果存入A1
3. 咱們再將6.99與1.06相乘並將答案存入A2
4. 最後將A1與A2相加並將結果放入A1
咱們能夠將這種操做順序書寫以下:
4.99 1.06 * 5.99 + 6.99 1.06 * + 這種記法叫作後綴(prefix)或逆波蘭式(reverse Polish)記法,其求值過程剛好就是咱們上述所描述的過程。計算這個問題最簡單的方法就是使用一個棧。當見到一個數時就把它推入棧中;
在遇到一個運算符時將該運算符就做用在從該棧彈出的兩個數(符號)上,將所得結果推入棧中,例如,後綴表達式:
*6 5 2 3 + 8 * + 3 + **
計算以下:前面四個字符放入棧中,此時棧變成了算法

Top  |     3      |
    ------->|------------|
            |     2      |
            |------------|
            |     5      |
            |------------|
            |     6      |
            +------------+

下面讀到一個「+」號,因此3和2從棧中彈出,而且他們的結果5被壓入棧中。code

Top  |     5      |
    ------->|------------|
            |     5      |
            |------------|
            |     6      |
            +------------+

接着,8進棧。it

Top  |     8      |
    ------->|------------|
            |     5      |
            |------------|
            |     5      |
            |------------|
            |     6      |
            +------------+

如今見到一個「*」號,所以8和5彈出,而且8 * 5 = 40 進棧。io

Top  |    40      |
    ------->|------------|
            |     5      |
            |------------|
            |     6      |
            +------------+

接着又見到一個「+」號,所以40和5彈出,而且40 + 5 = 45進棧。gc

Top  |    45      |
    ------->|------------|
            |     6      |
            +------------+

如今將3壓入棧中。方法

Top  |     3      |
    ------->|------------|
            |    45      |
            |------------|
            |     6      |
            +------------+

而後「+」是的3和45彈出,並將3 + 45 = 48壓入棧中。top

Top  |    48      |
    ------->|------------|
            |     6      |
            +------------+

最後,遇到一個「*」號,從棧中彈出48和6,將結果48 * 6 = 288壓入棧中。di

Top  |    288     |
    ------->+------------+

計算一個後綴表達式花費的時間是O(N),所以對輸入中的每一個元素的處理都是由一些棧操做組成從而花費常數時間。該算法的計算是很是簡單的。
注意:當一個表達式之後綴記號給出時,沒有必要知道任何優先規則。這是一個明顯的優勢。文件

中綴到後綴的轉換

棧不只能夠用來計算後綴表達式的值,並且咱們還能夠用棧將一個標準形式的表達式(或叫中綴(infix))轉換成後綴表達式。咱們經過只容許操做+,,(,),並堅持普通的優先級法則而將
通常的問題濃縮成小規模的問題。咱們還有進一步假設表達式是合法的。設咱們欲將中綴表達式
a + b * c + (d * e + f) * g
轉換成後綴表達式。正確的答案是
a b c * + d e * f + g * +
當讀到一個操做數的時候,當即把它放到輸出中。操做符不當即輸出,從而必須先存在某個地方。正確的作法是將已經見過的操做符放到棧中而不是輸出中。當遇到左圓括號時咱們也要將其推入棧中。
咱們從一個空棧開始計算。
若是見到一個右括號,那麼就將棧頂元素彈出,將彈出的符號寫入直到咱們遇到一個(對應的)左括號。可是這個左括號只被彈出,並不輸出。
若是咱們見到任何其餘的符號(「+」,「
」,「(」),那麼咱們將棧中彈出棧元素直至發現優先級更低的元素爲止。有一個例外:除非是處理一個「)」的時候,不然咱們毫不從棧中移走「(」。
對於這種操做,「+」的優先級最低,而「(」的優先級最高。當從棧中彈出元素的工做完成後,咱們再將操做符壓入棧中。
最後,若是咱們讀到輸入的末尾,咱們就將棧元素彈出直至該棧變成空棧,將符號寫到輸出中。
爲了理解這種算法的運行機制,咱們將把上面的中綴表達式轉換成後綴形式。
首先,a被讀入,因而它流向輸出。而後,「+」被讀入併入棧。接着b被讀入並流向輸出。這一時刻的狀態入下:時間

|            |              +---------------------------+                           
            |     +      |              | a b                       |
            +------------+              +---------------------------+
                 Stack                              Output

這時「」被讀入,操做符棧頂元素「+」比「」的優先級低,故沒有輸出,「*」入棧。接着c被讀入並流向輸出。至此,咱們有

|     *      |
            |------------|              +---------------------------+                        
            |     +      |              | a b c                     |
            +------------+              +---------------------------+
                 Stack                              Output

後面的符號是一個「+」號,檢查一下,咱們發現,須要將「*」從棧彈出並放到輸出中;彈出棧中剩下的「+」,該運算符不比剛剛遇到的「+」號優先級低而是有相同的優先級,隱藏也被彈出病輸出
而後,將剛剛遇到的「+」號放入棧中。

|            |              +---------------------------+                           
            |     +      |              | a b c * +                 |
            +------------+              +---------------------------+  
                 Stack                              Output

下一個被讀到的符號是一個「(」,因爲有最高的優先級,所以它被放入棧中。而後,d被讀入並輸出。

|     (      |
            |------------|             +---------------------------+                          
            |     +      |             | a b c * + d               |
            +------------+             +---------------------------+ 
                 Stack                              Output

繼續進行,咱們又讀到一個「*」。除非正在處理閉括號,不然開括號不會從棧中彈出,所以,沒有輸出,下一個是e,它被讀入並輸出。

|     *      |
            |------------|
            |     (      |
            |------------|             +---------------------------+                          
            |     +      |             | a b c * + d e             |
            +------------+             +---------------------------+
                 Stack                             Output

在日後咱們讀到的符號是「+」號,咱們將「*」彈出,而後將「+」壓入棧中。這之後,咱們讀入f並輸出。

|     +      |
            |------------|
            |     (      |
            |------------|             +---------------------------+                           
            |     +      |             | a b c * + d e * f         |
            +------------+             +---------------------------+  
                 Stack                             Output

如今,咱們讀入「)」,所以將棧中元素直到「(」彈出,咱們將一個「+」號輸出。

|            |              +---------------------------+                           
            |     +      |              | a b c * + d e * f +       |
            +------------+              +---------------------------+  
                 Stack                             Output

下面又讀到一個「*」,該運算符被壓入棧中。而後,g被讀入並輸出。

|     *      |
            |------------|              +---------------------------+                           
            |     +      |              | a b c * + d e * f + g     |
            +------------+              +---------------------------+  
                 Stack                              Output

如今輸入爲空,所以咱們將棧中全部符號都彈出並輸出,直到棧變成空棧。

|            |              +---------------------------+                           
            |            |              | a b c * + d e * f + g * + |
            +------------+              +---------------------------+  
                 Stack                             Output

與前面相同,這種轉換也只須要O(N)時間並通過一趟輸入後工做完成。咱們能夠經過指定減法和加法有相同的優先級以及乘法與除法有相同的優先級從而將減法與除法添加到指令集中。
一種巧妙的想法是將表達式「a - b - c」轉換成「a b - c -」而不是轉換成「a b c - -」

頭文件

// 3.3.3.2 中綴轉後綴
extern void Infix2Prefix(const char *);
// 是否爲操做符
int IsOpSymbols(const char);
// 獲取操做符優先級
int GetOpLevel(const char);
// 操做符優先級比對
int CompareOpLevel(const char, const char);

源代碼

/**
 * 3.3.3.2 中綴轉後綴
 * @todo
 * @param 
 */
void Infix2Prefix(const char *infix) {
	if (IsSymolsMatch(infix) == 0) {
		FatalError("The infix is not match.");
	}

	printf("The infix : %s\n", infix);
	printf("The prefix: ");

	Stack S = CreateStack();
	char curr, top;
	while (*infix) {
		curr = *infix;

		// 非操做符直接讀入並流向輸出
		if ( ! IsOpSymbols(curr)) {
			putchar(curr);
			infix++;
			continue;
		}

		// 空棧或「(」直接入棧
		if (IsEmptyStack(S) || curr == '(') {
			Push(curr, S);
			infix++;
			continue;
		}

		// 讀入「)」,所以將棧中元素直到「(」彈出
		if (curr == ')') {
			do {
				if (IsEmptyStack(S)) {
					break;
				}

				top = Top(S);

				putchar(' ');
				putchar(top);
				Pop(S);
			} while(top == '(');

			Pop(S);
			infix++;
			continue;
		}

		// 操做符優先級比對
		do {
			if (IsEmptyStack(S)) {
				break;
			}

			top = Top(S);
			if ( ! CompareOpLevel(top, curr) || top == '(') {
				break;
			}

			putchar(' ');
			putchar(top);
			Pop(S);
		} while(CompareOpLevel(top, curr));

		Push(curr, S);
		infix++;
	}

	// 全部棧元素輸出
	do {
		if (IsEmptyStack(S)) {
			break;
		}

		top = Top(S);

		putchar(' ');
		putchar(top);
		Pop(S);
	} while (top);
}

/**
 * 獲取操做符優先級
 * @param op
 * @return 
 */
int GetOpLevel(const char op) {
	switch (op) {
		case '-':
		case '+':
			return 1;
			break;
		case '*':
		case '/':
			return 2;
			break;
		case '(':
			return 3;
			break;
	}
}

/**
 * 操做符優先級比對
 * @param c
 * @param s
 * @return 
 */
int CompareOpLevel(const char c, const char s) {
	return GetOpLevel(c) >= GetOpLevel(s);
}

/**
 * 是否爲操做符
 * @param c
 * @return 
 */
int IsOpSymbols(const char c) {
	return c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')';
}

調用示例 -- 中綴表達式轉後綴表達式

#include <stdio.h>
#include <stdlib.h>

#include "ext/s15/stack.h"

int main(int argc, char** argv) {
	char str[] = "a + b * c + (d * e + f) * g";
	Infix2Prefix(str);

	return (EXIT_SUCCESS);
}
相關文章
相關標籤/搜索