13.3 表達式樹
算術表達式可以用二叉樹的形式表示,稱爲表達式樹(expression tree)。其操作符位於內結點,而操作數位於葉結點。表達式樹可以用於對表達式本身求值,也可以將中序表達式轉變成前序表達式或者後序表達式。
13.3.1 表達式樹ADT
算術操作符可分爲一元操作符(-a, a!)和二元操作符(a + b)。這裏,先只考慮二元操作符。將操作符儲存在根結點,而操作符左邊部分則儲存在左子樹中,右邊部分則儲存在右子樹中。
其所具有方法包括:
- ExpressionTree(expStr):將輸入的表達式字符串轉變成表達式樹,假定輸入的表達式字符串是合法的,並帶有全括號的表達式;
- evaluate(varDict):對表達式樹進行求值,並返回數值結果,若表達式樹中含有變量,則從輸入的字典中,對變量進行取值,除以零或使用未定義變量將會引發異常;
- toString():將表達式樹轉變成表達式字符串。
將表達式樹轉變成表達式字符串
對表達式進行後序遍歷,即可發現後序遍歷得到的正好就是表達式的後綴表示法。相應的,前序遍歷,得到的正好就是表達式的前綴表示法。
對於中序遍歷,它能得到表達式對於其內操作符以及操作數的正確運算順序,只是我們需要將其用括號括起來。
利用表達式樹對表達式求值
每一個子樹代表了表達式中的某一個子表達式,且其所處的層數越接近葉結點,則其運算優先級越高。所以對於一個根結點,可以先求出左子樹的值和右子樹的值,再用這兩個值與根結點處的運算符進行運算。
表達式樹的構建
爲了簡單起見,假定傳入的表達式滿足以下條件:
- 表達式內無空格存在;
- 表達式合法且,全括號;
- 每一個操作數均是一位數或者一個字母;
- 運算符爲 + - * / %。
當遍歷至'(‘時,創建一個新的結點,並將其作爲根結點的左子結點,同時將外部指針current移動至新創建的左子結點處。
當遍歷至數字時,將外部指針current所指向的結點處的值修改爲相應的數字,然後將外部指針current返回至根結點處。
當遍歷至操作符時,創建一個新結點,並將其作爲根結點的右子結點,同時將外部指針current移動至新創建的右子結點處。
再次遍歷至操作數時,將外部指針current所指向的結點,其值修改爲相應值,同時將外部指針current返回至該結點的根結點。
當遍歷至')'時,將外部指針current指向None即可。
當有多重運算時,例如:((2*7)+8)
#-*-coding: utf-8-*-
# 表達式樹ADT
from llistqueue import Queue
class ExpressionTree(object):
def __init__(self, expStr):
self._expTree = None
self._bulidTree(expStr)
def evaluate(self, varMap):
return self._evalTree(self._expTree, varMap)
def __str__(self):
return self._buildString(self._expTree)
# 對於中序遍歷,它能得到表達式對於其內操作符以及操作數的正確運算順序,只是我們需要將其用括號括起來。
def _buildString(self, treeNode):
if treeNode.left is None and treeNode.right is None:
return str(treeNode.element)
else:
expStr = '('
expStr += self._buildString(treeNode.left)
expStr += str(treeNode.element)
expStr += self._buildString(treeNode.right)
expStr += ')'
return expStr
# 這裏使用的是後序遍歷,只是訪問操作變成了求值而已
def _evalTree(self, subtree, varDict):
if subtree.left is None and subtree.right is None: # 判斷是否是葉結點
if '0' <= subtree.element <= '9': # 葉結點處肯定是操作數
return int(subtree.element)
else:
assert subtree.element in varDict, "Invalid variable."
return varDict[subtree.element]
else:
lvalue = _evalTree(subtree.left) # 左子樹的操作數
rvalue = _evalTree(subtree.right) # 右子樹的操作數
return self._computeOp(lvalue, subtree.element, rvalue) # 將兩個操作數與根結點處的操作符進行運算
# 輔助方法
def _computeOp(self, left, op, right):
if op == '+':
return left + right
elif op == '-':
return left - right
elif op == '*':
return left * right
elif op == '/':
assert right != 0, "divisor cannot be zero."
return 1.0 * left / right
elif op == '%':
assert right != 0, "divisor cannot be zero."
return left % right
def _bulidTree(self, expStr):
# 創建相應的隊列
expQ = Queue()
for token in expStr:
expQ.enqueue(token)
# 創建一個空結點,並作爲根結點
self._expTree = _ExpTreeNode(None)
# 調用遞歸函數創建表達式樹
self._recBuildTree(self._expTree, expQ)
# 用於創建表達式樹的遞歸函數
def _recBuildTree(self, curNode, expQ):
# 先從表達式中提取一個字符
token = expQ.dequeue()
# 查看相應的字符是否是'(',因爲'
if token == '(':
curNode.left = _ExpTreeNode(None) # 創建空結點作爲根結點的左子結點
self._recBuildTree(curNode.left, expQ) # 創建左子樹
# 此時下一個字符肯定是運算符
curNode.data = expQ.dequeue()
curNode.right = _ExpTreeNode(None) # 創建空結點作爲根結點的右子結點
self._recBuildTree(curNode.right, expQ) # 創建右子樹
# 此時下一個結點肯定是')',僅僅將它出隊即可
expQ.dequeue()
# 如果碰到一個數字或者字母,則修改相應結點值
else:
curNode.element = token
# 表達式樹結點的儲存類
class _ExpTreeNode(object):
def __init__(self, data):
self.element = data
self.left = None
self.right = None