數據結構基礎溫故-4.樹與二叉樹(下)

上面兩篇咱們瞭解了樹的基本概念以及二叉樹的遍歷算法,還對二叉查找樹進行了模擬實現。數學表達式求值是程序設計語言編譯中的一個基本問題,表達式求值是棧應用的一個典型案例,表達式分爲前綴、中綴和後綴三種形式。這裏,咱們經過一個四則運算的應用場景,藉助二叉樹來幫助求解表達式的值。首先,將表達式轉換爲二叉樹,而後經過先序遍歷二叉樹的方式求出表達式的值。html

1、二叉樹如何表示四則運算

1.1 表達式轉換爲二叉樹

  上圖是表達式「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。設計

1.2 二叉表達式樹的構造過程解析

   從上面的解析過程能夠看出,這是一個遞歸的過程,正好能夠用二叉樹先序遍歷的方法進行計算。下面咱們來一步一步地經過圖示來演示一下表達式"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)】

2、二叉表達式樹的模擬實現

2.1 二叉表達式樹節點的定義

        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();
                }
            }
        }
View Code

  與普通二叉樹節點定義不一樣,這裏新增了一個isOptr標誌,來判斷該節點是數字節點仍是運算符節點;

2.2 二叉表達式樹的建立實現

        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;
        }

  這裏按照咱們在上面所概括的建立過程算法進行了實現,代碼中的註釋已經比較完善,這裏就再也不贅述。

2.3 二叉表達式的先序遍歷計算運算結果實現

        // 先序遍歷進行表達式求值
        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;
        }

  這裏經過遞歸地進行先序遍歷,也就是求得根節點(運算符)的兩個子樹的值,最後再經過對這兩個值進行根節點運算符的計算獲得最終的結果。

2.4 四則運算運行結果

  因爲本表達式樹的設計較爲簡單,沒有考慮到帶括號的情形,所以這裏只用不帶括號的表達式進行查看,運行結果以下圖所示:

  (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,《算術表達式—二叉樹

 

相關文章
相關標籤/搜索