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


前面所討論的線性表元素之間都是一對一的關系,今天我們所看到的結構各元素之間卻是一對多的關系。樹在計算機中有着廣泛的應用,甚至在計算機的日常使用中,也可以看到樹形結構的身影,如下圖所示的Windows資源管理器和應用程序的菜單都屬於樹形結構。樹形結構是一種典型的非線性結構,除了用於表示相鄰關系外,還可以表示層次關系。本文重點討論樹與二叉樹的基本結構和遍歷算法等內容。

一、好大一棵樹,綠色的祝福

1.1 樹的基本概念

Defination:樹(Tree)是 n(n≥0)個結點的有限集。n=0時,該樹被稱為“空樹”。如上圖所示,A點稱為根節點,它有兩棵子樹,分別以B、C為根,而以C為根的子樹又可以分成兩棵子樹。  

1.2 樹的基本術語

  (1)不同的節點:根節點、內部節點、葉子節點以及節點的

  (2)節點的關系:雙親與孩子,爸爸回來了,爸爸去哪兒?

  (3)節點的層次:結點的層次(Level)從根開始定義起,根為第一層,根的孩子為第二層。樹中結點的最大層次稱為樹的深度(Depth)或高度

二、二叉樹又是個什么鬼

2.1 從猜數字游戲引出二叉樹

  回憶一下,當年某電視節目中會讓游戲參與者猜一個產品的價格,如果參與者在限定時間內猜對了,那么他就可以獲得這個產品。很多人都是一點點的提高數值來猜,但是這樣猜會很沒有效率。因此,很多聰明人都知道需要利用折半查找的思想去猜測。假定某個產品在100元的范圍內,那么可以在7次之內猜出結果來,如下圖所示:(由於是100以內的正整數,所以我們先猜50(100的一半),被告之“大了”,於是再猜25(50的一半),被告之“小了”,再猜37(25與50的中間數),小了,於是猜43,大了,40,大了,38,小了,39,完全正確。)

  如上圖所示,對於這種在某個階段都是兩種結果的情形,比如開和關、0和1、真和假、上和下、對與錯,正面與反面等,都適合用樹狀結構來建模,而這種樹是一種很特殊的樹狀結構,叫做二叉樹。

二叉樹的特點:

①每個結點最多有兩棵子樹,所以二叉樹中不存在度大於2的結點。

②左子樹和右子樹是有順序的,次序不能任意顛倒。

③即使樹中某結點只有一棵子樹,也要區分它是左子樹還是右子樹。

2.2 二叉樹的順序存儲結構

  二叉樹的順序存儲結構就是用一維數組存儲二叉樹中的結點。結點的存儲位置,也就是數組的下標要能體現結點之間的邏輯關系,比如雙親與孩子的關系,左右兄弟的關系等。

  But,考慮一種極端的情況,一棵深度為k的右斜樹,它只有k個結點,卻需要分配2的k次方-1個存儲單元空間,這顯然是對存儲空間的浪費,所以,順序存儲結構一般只適用於完全二叉樹

2.3 二叉樹的鏈式存儲結構

  既然順序存儲適用性不強,我們就要考慮鏈式存儲結構。二叉樹每個結點最多有兩個孩子,所以為它設計一個數據域和兩個指針域是比較自然的想法,我們稱這樣的鏈表叫做二叉鏈表。其中data是數據域,lchild和rchild都是指針域,分別存放指向左孩子和右孩子的指針。

三、二叉樹的代碼實現

3.1 二叉樹的C#代碼實現

  (1)二叉樹節點的定義:

    /// <summary>
    /// 二叉樹的節點定義
    /// </summary>
    /// <typeparam name="T">數據具體類型</typeparam>
    public class Node<T>
    {
        public T data { get; set; }

        public Node<T> lchild { get; set; }

        public Node<T> rchild { get; set; }

        public Node()
        {
        }

        public Node(T data)
        {
            this.data = data;
        }

        public Node(T data, Node<T> lchild, Node<T> rchild)
        {
            this.data = data;
            this.lchild = lchild;
            this.rchild = rchild;
        }
    }
View Code

  (2)二叉樹的創建實現:

        // Method01:判斷該二叉樹是否是空樹
        public bool IsEmpty()
        {
            return this.root == null;
        }

        // Method02:在節點p下插入左孩子節點的data
        public void InsertLeft(Node<T> p, T data)
        {
            Node<T> tempNode = new Node<T>(data);
            tempNode.lchild = p.lchild;

            p.lchild = tempNode;
        }

        // Method03:在節點p下插入右孩子節點的data
        public void InsertRight(Node<T> p, T data)
        {
            Node<T> tempNode = new Node<T>(data);
            tempNode.rchild = p.rchild;

            p.rchild = tempNode;
        }

        // Method04:刪除節點p下的左子樹
        public Node<T> RemoveLeft(Node<T> p)
        {
            if (p == null || p.lchild == null)
            {
                return null;
            }

            Node<T> tempNode = p.lchild;
            p.lchild = null;
            return tempNode;
        }

        // Method05:刪除節點p下的右子樹
        public Node<T> RemoveRight(Node<T> p)
        {
            if (p == null || p.rchild == null)
            {
                return null;
            }

            Node<T> tempNode = p.rchild;
            p.rchild = null;
            return tempNode;
        }
View Code

  以上四個方法分別提供了新節點的插入以及移除的實現,我們可以針對某個節點進行插入左孩子有右孩子節點。

  (3)二叉樹的遞歸遍歷:

  首先我們通過幾張圖來看看二叉樹的三種基本遍歷:前序、中序以及后序遍歷;

  ①前序遍歷:若根節點不為空,則先訪問根節點,然后先序遍歷左子樹,最后先序遍歷右子樹;

  ②中序遍歷:若根節點不為空,則先中序遍歷左子樹,再訪問根節點,最后中序遍歷右子樹;

Mid Order

  ③后序遍歷:若根節點不為空,則首先后序遍歷左子樹,其次后序遍歷右子樹,最后訪問根節點;

Post Order

        // Method01:前序遍歷
        public void PreOrder(Node<T> node)
        {
            if (node != null)
            {
                // 根->左->右
                Console.Write(node.data + " ");
                PreOrder(node.lchild);
                PreOrder(node.rchild);
            }
        }

        // Method02:中序遍歷
        public void MidOrder(Node<T> node)
        {
            if (node != null)
            {
                // 左->根->右
                MidOrder(node.lchild);
                Console.Write(node.data + " ");
                MidOrder(node.rchild);
            }
        }

        // Method03:后序遍歷
        public void PostOrder(Node<T> node)
        {
            if (node != null)
            {
                // 左->右->根
                PostOrder(node.lchild);
                PostOrder(node.rchild);
                Console.Write(node.data + " ");
            }
        } 
View Code

  本次實現采用了遞歸的方式實現遍歷算法,主要是根據二叉樹三種遍歷(前序、中序以及后序遍歷)的要求,依次輸出各個節點的元素。至於非遞歸方式的遍歷算法以及層次遍歷算法會在下一篇中進行介紹。

3.2 測試二叉樹的遍歷方法

  在上面的代碼中,我們實現了二叉樹的遞歸遍歷算法,這里我們通過一段簡單的測試代碼來構造一顆二叉樹,並進行遍歷。首先,通過下圖看看我們要創建的一顆二叉樹是什么鬼?

  (1)測試代碼:

        static void MyBinaryTreeBasicTest()
        {
            // 構造一顆二叉樹,根節點為"A"
            MyBinaryTree<string> bTree = new MyBinaryTree<string>("A");
            Node<string> rootNode = bTree.Root;
            // 向根節點"A"插入左孩子節點"B"和右孩子節點"C"
            bTree.InsertLeft(rootNode, "B");
            bTree.InsertRight(rootNode, "C");
            // 向節點"B"插入左孩子節點"D"和右孩子節點"E"
            Node<string> nodeB = rootNode.lchild;
            bTree.InsertLeft(nodeB, "D");
            bTree.InsertRight(nodeB, "E");
            // 向節點"C"插入右孩子節點"F"
            Node<string> nodeC = rootNode.rchild;
            bTree.InsertRight(nodeC, "F");

            // 前序遍歷
            Console.WriteLine("---------PreOrder---------");
            bTree.PreOrder(bTree.Root);
            // 中序遍歷
            Console.WriteLine();
            Console.WriteLine("---------MidOrder---------");
            bTree.MidOrder(bTree.Root);
            // 后序遍歷
            Console.WriteLine();
            Console.WriteLine("---------PostOrder---------");
            bTree.PostOrder(bTree.Root);
        }

  (2)運行結果:

附件下載

  本文實現的C#版二叉樹參考代碼下載:http://pan.baidu.com/s/1eQ1xmXs

參考資料

(1)程傑,《大話數據結構》

(2)陳廣,《數據結構(C#語言描述)》

(3)段恩澤,《數據結構(C#語言版)》

(4)楊俊明,《數據結構C#版筆記—樹與二叉樹

(5)Frank Fan,《萬丈高樓平地起之C#實現二叉樹操作

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM