【數據結構】複雜表達式的求值

參考博客:前綴、中綴、後綴表達式

一、表達式的三種表示法

1.  中綴表示法 
     運算符放在兩個運算對象中間,這是我們書寫的時候最熟悉的一種形式,如:(2 + 3)* 7
2.  前綴表示法
     前綴是附加在表達式前面的內容。又稱爲波蘭前綴表示法,因爲他是由波蘭數學家Jan Lukasiewicz發明的。使用前綴表示法後,就完全不需要括號了。例如,表達式(2 + 3)*  7以波蘭前綴表示法可以寫成: * + 2 3 7
     計算方法:
     算波蘭表達式時,無需記住運算的層次,只需要直接尋找第一個運算的操作符。以二元運算爲例,從左至右讀入表達式,遇到一個操作符後跟隨兩個操作數時,則計算之,然後將結果作爲操作數替換這個操作符和兩個操作數;重複此步驟,直至所有操作符處理完畢。因爲在正確的前綴表達式中,操作數必然比操作符多一個,所以必然能找到一個操作符符合運算條件;而替換時,兩個操作數和一個操作符替換爲一個操作數,所以減少了各一個操作符和操作數,仍然可以迭代運算直至計算整個式子。多元運算也類似,遇到足夠的操作數即產生運算,迭代直至完成。迭代結束的條件由表達式的正確性來保證。下面是一個例子,演示了每一步的運算順序:

− × ÷ 15 − 7 + 1 1 3 + 2 + 1 1 =
− × ÷ 15 − 7 2       3 + 2 + 1 1 =
− × ÷ 15  5            3 + 2 + 1 1 =
− × 3                     3 + 2 + 1 1 =
− 9                           + 2 + 1 1 =
− 9                                 + 2 2 =
− 9                                       4 =
5


等價的中綴表達式:  ((15 ÷ (7 − (1 + 1))) × 3) − (2 + (1 + 1)) = 5

3.後綴表達式
    後綴表達式與前綴表達式類似,只是運算符位於操作數之後。
    他和前綴表示法都對計算機比較友好,但他很容易用堆棧解析,所以在計算機中用的很多。他的解釋過程一般是:操作數入棧;遇到操作符時,操作數出棧,求值,將結果入棧;當一遍後,棧頂就是表達式的值。因此逆波蘭表達式的求值使用堆棧結構很容易實現,和能很快求值。 注意:逆波蘭記法並不是簡單的波蘭表達式的反轉。因爲對於不滿足交換律的操作符,它的操作數寫法仍然是常規順序,如,波蘭記法“/ 6 3”的逆波蘭記法是“6 3 /”而不是“3 6 /”;數字的數位寫法也是常規順序。 爲了更好的瞭解前綴表達式的計算過程,舉個例子:5 3 2 * + 4 - 5 +,計算過程如下:
後綴表達式的求值
掃描到的元素 棧(棧底到棧頂)                   說明
5 5 操作數直接入棧
3 5 3 操作數直接入棧
2 5 3 2 操作數直接入棧
* 5 6 遇到操作符,彈出兩個實數,求值,壓棧
+ 11 遇到操作符,彈出兩個實數,求值,壓棧
4 11 4 操作數直接入棧
- 7 遇到操作符,彈出兩個實數,求值,壓棧
5 7 5 操作數直接入棧
+ 12 計算結果爲12

二 、 算法實現

	public static final Pattern UNSIGNED_DOUBLE = 
			Pattern.compile("((\\d+\\.?\\d*)|(\\.\\d+))([Ee][-+]?\\d+)?.*?");
	public static final Pattern CHARACTER = 
			Pattern.compile("\\S.*?");
1. 後綴表達式求值

僞代碼:
a. 初始化生成了一個由雙精度實數構成的棧。
b. do 
if(下一個輸入元素爲數值)
讀取下一個元素並壓到棧中。
else
   {
           讀取下一個字符,該字符是一個運算符。
           從棧中彈出兩個實數
           將兩個數用運算符組合(第二個彈出的數值爲左操作數)
然後將結果壓入棧中。
    }
       while (表達式中有更多的輸入)。
c. 到此爲止,棧中僅包含一個數值,該數值就是表達式的值。
/**
	 * 後綴表達式的求值算法
	 * @param expression
	 * @return
	 */
	public static double evaluateReversePolishNotation(String expression)
	{
		// 使用一個棧來保存表達式中的數值
		Stack<Double> numbers = new Stack<Double>();
		
		// 將表達式轉換成一個Scanner類的對象,以便更易於處理
		Scanner input = new Scanner(expression);
		// next存儲的是表達式的下一個片段:數值、運算符或括號
		String next;
		
		while (input.hasNext())
		{
			if (input.hasNext(UNSIGNED_DOUBLE))
			{
				next = input.findInLine(UNSIGNED_DOUBLE);
				numbers.push(new Double(next));
			} else {
				next = input.findInLine(CHARACTER);
				double operand1, operand2;
				
				if (numbers.size() < 2)
				{
					throw new IllegalArgumentException("Illegal expression.");
				}
				
				switch (next.charAt(0))
				{
					case '+':
						operand2 = numbers.pop();
						operand1 = numbers.pop();
						numbers.push(operand1 + operand2);
						break;
					case '-':
						operand2 = numbers.pop();
						operand1 = numbers.pop();
						numbers.push(operand1 - operand2);
						break;
					case '*':
						operand2 = numbers.pop();
						operand1 = numbers.pop();
						numbers.push(operand1 * operand2);
						break;
					case '/':
						operand2 = numbers.pop();
						operand1 = numbers.pop();
						numbers.push(operand1 / operand2);
						break;
				default:
					throw new IllegalArgumentException("Illegal input expression.");
				}
			}
		}
		if (numbers.size() != 1)
		{
			throw new IllegalArgumentException("Illegal input expression.");
		}
		return numbers.pop();
	}
		String s3 = "5 3 2 *+4-5+";
		double value3 = CalculateUtils.evaluateReversePolishNotation(s3);
		System.out.println(value3);
5 3 2 *+4-5+的計算結果爲:12.0

2. 中綴表達式求值
        由於後綴表達式非常易於求值,所以計算普通的中綴表達式的一種策略是先將它轉載成後綴表達式,然後在計算對應的後最表達式。
僞代碼:
 a. 初始化一個字符棧用於存儲運算符和括號。
 b. do
        if (下一個輸入時左括號)
             讀取左括號並將它壓到棧中。
        else if (下一個輸入時數值或其他操作數)
             讀取操作數並將它輸出
        else if (下一個輸入是運算符)
                 {
                        出棧並打印運算符,直到下列三種情況中的一種發生:(1) 棧爲空;(2)棧中的下一個符號是左括號;(3)棧中的下一個符號是優                           先級比下一個輸入中的運算符更低的運算符。當發生三種情況中的某種時,停止出棧,讀取下一個輸入符號,並將該符號壓入棧中。
                 }
                  else 
                  {
                        讀取並放棄下一個輸入符號(它應該是一個右括號)。出棧並打印輸出從棧中彈出的運算符,直到棧中的下一個符號是左括號位置。                            (如果沒有出現左括號,打印出出錯信 息表明括號不平衡,終止程序。)最後,出棧並丟棄左括號。
                   }
              while  (表達式中有更多信息讀入)
         c. 出棧並打印輸出棧中所有屬於的運算符。(不應該是左括號,否則輸入表達式的括號是不平衡的。)
// 中綴表達式轉換爲波蘭後綴表達式的算法
	public static String infixToReversePolishNotation(String expression)
	{
		StringBuffer outStrBuffer = new StringBuffer();
		
		// 使用一個棧來存儲運算符和括號
		Stack<Character> operations = new Stack<Character>();
		
		// 將表達式轉換成一個Scanner類的對象,以便更易於處理
		Scanner input = new Scanner(expression);
		// next存儲的是表達式的下一個片段:數值、運算符或括號
		String next;
		
		while (input.hasNext())
		{
			// 如果輸入爲數值或其他操作數,讀取操作數並將它
			if (input.hasNext(UNSIGNED_DOUBLE))
			{
				next = input.findInLine(UNSIGNED_DOUBLE);
				outStrBuffer.append(next);
				outStrBuffer.append(" ");
			} else {
				next = input.findInLine(CHARACTER);
				
				switch (next.charAt(0))
				{
					case '(':
						operations.push(next.charAt(0));
						break;
					case '+':
					case '-':
					case '*':
					case '/':
						while (true)
						{
							if (operations.empty() || operations.peek().equals('(') || 
									priorityCompare(operations.peek(), next.charAt(0)) == -1)
							{
								operations.push(next.charAt(0));
								break;
							}
							else {
								outStrBuffer.append(operations.pop());
								outStrBuffer.append(" ");
							}
						}
						break;
					case ')':
						while (true)
						{
							if (!operations.peek().equals('('))
							{
								outStrBuffer.append(operations.pop());
								outStrBuffer.append(" ");
							} else {
								operations.pop();
								break;
							}
						}
						break;
				default:
					throw new IllegalArgumentException("Illegal input expression.");
				}
			}
		}
		String s4 = "5*(9.1+3.2)/(1-5+4.88)";
		String outStr = infixToReversePolishNotation(s4);
		System.out.println(outStr);
		
		double value4 = CalculateUtils.evaluateReversePolishNotation(outStr);
		System.out.println(value4);
結果:
5 9.1 3.2 + * 1 5 - 4.88 + /
69.88636363636364

 此程序是筆者爲了說明上述概念而編寫,僅做了簡單的測試,不保證其中沒有Bug,因此不要將其用於除研究之外的其他場合。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章