上面兩篇咱們瞭解了樹的基本概念以及二叉樹的遍歷算法,還對二叉查找樹進行了模擬實現。數學表達式求值是程序設計語言編譯中的一個基本問題,表達式求值是棧應用的一個典型案例,表達式分爲前綴、中綴和後綴三種形式。這裏,咱們經過一個四則運算的應用場景,藉助二叉樹來幫助求解表達式的值。首先,將表達式轉換爲二叉樹,而後經過先序遍歷二叉樹的方式求出表達式的值。html
上圖是表達式「3+2*9-16/4」轉換成的二叉樹,觀察表達式,能夠看出:node
(1)操做數都是葉子節點;算法
(2)運算符都是內部節點;express
(3)優先運算的操做符都在樹下方,而相對優先級較低的減法(根節點)運算則最後運算。數據結構
從上往下看,這棵二叉樹能夠理解以下:ide
(1)要理解根節點"-"號的結果必須先計算出左子樹"+"和右子樹"/"號的結果。能夠看,要想獲得"+"號的結果,又必須先計算其右子樹"*"號的結果;this
(2)"*"號左右孩子是數字,能夠直接計算,2*9=18。接下來計算"+"號,3+18=21,即根節點的左子樹結果爲21;spa
(3)"/"號左右孩子是數字,能夠直接計算,16/4=4。因而,根節點的右子樹結果爲4。.net
(4)最後計算根節點的"-"號,21-4=17,因而得出了該表達式的值爲17。設計
從上面的解析過程能夠看出,這是一個遞歸的過程,正好能夠用二叉樹先序遍歷的方法進行計算。下面咱們來一步一步地經過圖示來演示一下表達式"3+2*9-16/4"解析生成二叉樹的過程。
(1)首先獲取表達式的第一個字符「3」,因爲表達式樹目前仍是一棵空樹,因此3成爲根節點;
(2)獲取第二個字符「+」,此時表達式樹根節點爲數字,須要將新節點做爲根節點,原根節點做爲新根節點的左孩子。這裏須要注意的是:只有第二個節點會出現這樣的可能,由於以後的根節點一定爲操做符;
(3)獲取第三個字符「2」,數字將沿着根節點右鏈插入到最右端;
(4)獲取第四個字符「*」,若是判斷到是操做符,則將與根節點比較優先級,若是新節點的優先級高則插入成爲根節點的右孩子,而原根節點的右孩子則成爲新節點的左子樹;
(5)獲取第五個字符「9」,數字將沿着根節點右鏈插入到最右端;
(6)獲取第六個字符「-」,「-」與根節點「+」比較運算符的優先級,優先級相等則新節點成爲根節點,原表達式樹則成爲新節點的左子樹;
(7)獲取第7與第8個字符組合爲數字16,沿着根節點右鏈插入到最右端;
(8)獲取第九個字符「/」,與根節點比較運算符的優先級,優先級高則成爲根節點的右孩子,原根節點右子樹則成爲新節點的左子樹;
(9)獲取第十個字符「4」,仍是沿着根節點右鏈查到最右端。至此,運算表達式已所有遍歷,一棵表達式樹就已經創建完成。
SUMMARY:從以上過程當中咱們能夠將表達式樹的創建算法歸結以下
①第一個節點先成爲表達式樹的根;
②第二個節點插入時變爲根節點,原根節點變爲新節點的左孩子;
③插入節點爲數字時,沿着根節點右鏈插入到最右端;
④插入節點爲操做符時,先跟根節點操做符進行對比,分兩種狀況進行處理:
一是當優先級不高時,新節點成爲根節點,原表達式樹成爲新節點的左子樹;【如上面的步驟(6)】
二是當優先級較高時,新節點成爲根節點右孩子,原根節點右子樹成爲新節點的左子樹。【如上面的步驟(8)】
private class Node { private bool _isOptr; public bool IsOptr { get { return _isOptr; } set { _isOptr = value; } } private int _data; public int Data { get { return _data; } set { _data = value; } } private Node _left; public Node Left { get { return _left; } set { _left = value; } } private Node _right; public Node Right { get { return _right; } set { _right = value; } } public Node(int data) { this._data = data; this._isOptr = false; } public Node(char optr) { this._isOptr = true; this._data = optr; } public override string ToString() { if (this._isOptr) { return Convert.ToString((char)this._data); } else { return this._data.ToString(); } } }
與普通二叉樹節點定義不一樣,這裏新增了一個isOptr標誌,來判斷該節點是數字節點仍是運算符節點;
private Node CreateTree() { Node head = null; while(_pos < _expression.Length) { Node node = GetNode(); // 將當前解析字符轉換爲節點 if(head == null) { head = node; } else if (head.IsOptr == false) // 根節點爲數字,當前節點爲根,原根節點變爲左孩子 { node.Left = head; head = node; } else if (node.IsOptr == false) // 若是當前節點是數字 { // 當前節點沿右路插入最右邊成爲右孩子 Node tempNode = head; while(tempNode.Right != null) { tempNode = tempNode.Right; } tempNode.Right = node; } else // 若是當前節點是運算符 { if (GetPriority((char)node.Data) <= GetPriority((char)head.Data)) // 優先級低則成爲根,原二叉樹成爲插入節點的左子樹 { node.Left = head; head = node; } else // 優先級高則成爲根節點的右子樹,原右子樹成爲插入節點的左子樹 { node.Left = head.Right; head.Right = node; } } } return head; }
這裏按照咱們在上面所概括的建立過程算法進行了實現,代碼中的註釋已經比較完善,這裏就再也不贅述。
// 先序遍歷進行表達式求值 private int PreOrderCalc(Node node) { int num1, num2; if (node.IsOptr) { // 遞歸先序遍歷計算num1 num1 = PreOrderCalc(node.Left); // 遞歸先序遍歷計算num2 num2 = PreOrderCalc(node.Right); char optr = (char)node.Data; switch (optr) { case '+': node.Data = num1 + num2; break; case '-': node.Data = num1 - num2; break; case '*': node.Data = num1 * num2; break; case '/': if (num2 == 0) { throw new DivideByZeroException("除數不能爲0!"); } node.Data = num1 / num2; break; } } return node.Data; }
這裏經過遞歸地進行先序遍歷,也就是求得根節點(運算符)的兩個子樹的值,最後再經過對這兩個值進行根節點運算符的計算獲得最終的結果。
因爲本表達式樹的設計較爲簡單,沒有考慮到帶括號的情形,所以這裏只用不帶括號的表達式進行查看,運行結果以下圖所示:
(1)3+2*9-16/4
(2)4*5-16/4+2*9
本文所實現的二叉表達樹求解四則運算的C#代碼:http://pan.baidu.com/s/1eQheNQy
(1)陳廣,《數據結構(C#語言描述)》
(2)隱約有歌,《C#實例講解二叉樹原理與實現》
(3)zhx6044,《棧和二叉樹的使用》
(4)zero516cn,《算術表達式—二叉樹》