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

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

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

1.1 樹的基本概念

Defination:樹(Tree)是 n(n≥0)個結點的有限集。n=0時,該樹被稱爲「空樹」。如上圖所示,A點稱爲根節點,它有兩棵子樹,分別以B、C爲根,而以C爲根的子樹又能夠分紅兩棵子樹。  node

1.2 樹的基本術語

  (1)不一樣的節點:根節點、內部節點、葉子節點以及節點的算法

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

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

2、二叉樹又是個什麼鬼

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

  回憶一下,當年某電視節目中會讓遊戲參與者猜一個產品的價格,若是參與者在限定時間內猜對了,那麼他就能夠得到這個產品。不少人都是一點點的提升數值來猜,可是這樣猜會很沒有效率。所以,不少聰明人都知道須要利用折半查找的思想去猜想。假定某個產品在100元的範圍內,那麼能夠在7次以內猜出結果來,以下圖所示:(因爲是100之內的正整數,因此咱們先猜50(100的一半),被告之「大了」,因而再猜25(50的一半),被告之「小了」,再猜37(25與50的中間數),小了,因而猜43,大了,40,大了,38,小了,39,徹底正確。)ide

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

二叉樹的特色:this

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

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

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

2.2 二叉樹的順序存儲結構

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

  But,考慮一種極端的狀況,一棵深度爲k的右斜樹,它只有k個結點,卻須要分配2的k次方-1個存儲單元空間,這顯然是對存儲空間的浪費,因此,順序存儲結構通常只適用於徹底二叉樹

2.3 二叉樹的鏈式存儲結構

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

3、二叉樹的代碼實現

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#實現二叉樹操做

 

相關文章
相關標籤/搜索