1. 程式人生 > >資料結構基礎溫故-4.樹與二叉樹(上)

資料結構基礎溫故-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)執行結果:

附件下載

參考資料

(1)程傑,《大話資料結構》

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

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

作者:周旭龍

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。

相關推薦

資料結構基礎-4.

上面兩篇我們瞭解了樹的基本概念以及二叉樹的遍歷演算法,還對二叉查詢樹進行了模擬實現。數學表示式求值是程式設計語言編譯中的一個基本問題,表示式求值是棧應用的一個典型案例,表示式分為字首、中綴和字尾三種形式。這裡,我們通過一個四則運算的應用場景,藉助二叉樹來幫助求解表示式的值。首先,將表示式轉換為二叉樹,然後通過

資料結構基礎-4.

在上一篇中,我們瞭解了樹的基本概念以及二叉樹的基本特點和程式碼實現,還用遞迴的方式對二叉樹的三種遍歷演算法進行了程式碼實現。但是,由於遞迴需要系統堆疊,所以空間消耗要比非遞迴程式碼要大很多。而且,如果遞迴深度太大,可能系統撐不住。因此,我們使用非遞迴(這裡主要是迴圈,迴圈方法比遞迴方法快, 因為迴圈避免了一系

資料結構基礎-4.

前面所討論的線性表元素之間都是一對一的關係,今天我們所看到的結構各元素之間卻是一對多的關係。樹在計算機中有著廣泛的應用,甚至在計算機的日常使用中,也可以看到樹形結構的身影,如下圖所示的Windows資源管理器和應用程式的選單都屬於樹形結構。樹形結構是一種典型的非線性結構,除了用於表示相鄰關係外,還可以表示層次

資料結構基礎-6.查詢:基本查詢表查詢

只要你開啟電腦,就會涉及到查詢技術。如炒股軟體中查股票資訊、硬碟檔案中找照片、在光碟中搜DVD,甚至玩遊戲時在記憶體中查詢攻擊力、魅力值等資料修改用來作弊等,都要涉及到查詢。當然,在網際網路上查詢資訊就更加是家常便飯。查詢是計算機應用中最常用的操作之一,也是許多程式中最耗時的一部分,查詢方法的優劣對於系統的執

資料結構基礎-5.圖:圖的遍歷演算法

上一篇我們瞭解了圖的基本概念、術語以及儲存結構,還對鄰接表結構進行了模擬實現。本篇我們來了解一下圖的遍歷,和樹的遍歷類似,從圖的某一頂點出發訪問圖中其餘頂點,並且使每一個頂點僅被訪問一次,這一過程就叫做圖的遍歷(Traversing Graph)。如果只訪問圖的頂點而不關注邊的資訊,那麼圖的遍歷十分簡單,使用

資料結構基礎-1.線性表

在上一篇中,我們學習了線性表最基礎的表現形式-順序表,但是其存在一定缺點:必須佔用一整塊事先分配好的儲存空間,在插入和刪除操作上需要移動大量元素(即操作不方便),於是不受固定儲存空間限制並且可以進行比較快捷地插入和刪除操作的連結串列橫空出世,所以我們就來複習一下連結串列。 一、單鏈表基礎 1.1 單鏈表的

資料結構基礎-6.查詢:雜湊表

雜湊(雜湊)技術既是一種儲存方法,也是一種查詢方法。然而它與線性表、樹、圖等結構不同的是,前面幾種結構,資料元素之間都存在某種邏輯關係,可以用連線圖示表示出來,而雜湊技術的記錄之間不存在什麼邏輯關係,它只與關鍵字有關聯。因此,雜湊主要是面向查詢的儲存結構。雜湊技術最適合的求解問題是查詢與給定值相等的記錄。

資料結構基礎-5.圖:最小生成樹演算法

圖的“多對多”特性使得圖在結構設計和演算法實現上較為困難,這時就需要根據具體應用將圖轉換為不同的樹來簡化問題的求解。 一、生成樹與最小生成樹 1.1 生成樹   對於一個無向圖,含有連通圖全部頂點的一個極小連通子圖成為生成樹(Spanning Tree)。其本質就是從連通圖任一頂點出發進行遍歷操作所經過

資料結構基礎-5.圖:圖的基本概念

前面幾篇已經介紹了線性表和樹兩類資料結構,線性表中的元素是“一對一”的關係,樹中的元素是“一對多”的關係,本章所述的圖結構中的元素則是“多對多”的關係。圖(Graph)是一種複雜的非線性結構,在圖結構中,每個元素都可以有零個或多個前驅,也可以有零個或多個後繼,也就是說,元素之間的關係是任意的。現實生活中的很多

資料結構基礎-5.圖:最短路徑

圖的最重要的應用之一就是在交通運輸和通訊網路中尋找最短路徑。例如在交通網路中經常會遇到這樣的問題:兩地之間是否有公路可通;在有多條公路可通的情況下,哪一條路徑是最短的等等。這就是帶權圖中求最短路徑的問題,此時路徑的長度不再是路徑上邊的數目總和,而是路徑上的邊所帶權值的和。帶權圖分為無向帶權圖和有向帶權圖,但如

資料結構基礎-2.棧

現實生活中的事情往往都能總結歸納成一定的資料結構,例如餐館中餐盤的堆疊和使用,羽毛球筒裡裝的羽毛球等都是典型的棧結構。而在.NET中,值型別線上程棧上進行分配,引用型別在託管堆上進行分配,本文所說的“棧”正是這種資料結構。棧和佇列都是常用的資料結構,它們的邏輯結構與線性表相通,不同之處則在於操作受某種特殊限制

資料結構基礎-1.線性表

在上一篇中,我們瞭解了單鏈表與雙鏈表,本次將單鏈表中終端結點的指標端由空指標改為指向頭結點,就使整個單鏈表形成一個環,這種頭尾相接的單鏈表稱為單迴圈連結串列,簡稱迴圈連結串列(circular linked list)。 一、迴圈連結串列基礎 1.1 迴圈連結串列節點結構   迴圈連結串列和單鏈表的

資料結構基礎-3.佇列

在日常生活中,佇列的例子比比皆是,例如在車展排隊買票,排在隊頭的處理完離開,後來的必須在隊尾排隊等候。在程式設計中,佇列也有著廣泛的應用,例如計算機的任務排程系統、為了削減高峰時期訂單請求的訊息佇列等等。與棧類似,佇列也是屬於操作受限的線性表,不過佇列是隻允許在一端進行插入,在另一端進行刪除。在其他資料結構如

資料結構基礎-7.排序

排序(Sorting)是計算機內經常進行的一種操作,其目的是將一組“無序”的記錄序列調整為按關鍵字“有序”的記錄序列。如何進行排序,特別是高效率地進行排序時計算機工作者學習和研究的重要課題之一。排序有內部排序和外部排序之分,若整個排序過程不需要訪問外存便能完成,則稱此類排序為內部排序,反之則為外部排序。本篇主

3、非線性結構--——數據結構基礎篇】

位置 enter 深度 基礎 表達式 左右 -a 基礎篇 先序遍歷 非線性結構--樹與二叉樹 二叉樹的基礎知識:         二叉樹的特點:             1、每個結點的度<=2             2、二叉樹是有序樹         二叉樹的五種不

Android版資料結構演算法(六):

/** * 前序遍歷——迭代 * @author Administrator * */ public void preOrder(TreeNode node){ if(node == null){ return;

資料結構

上面兩篇我們瞭解了樹的基本概念以及二叉樹的遍歷演算法,還對二叉查詢樹進行了模擬實現。數學表示式求值是程式設計語言編譯中的一個基本問題,表示式求值是棧應用的一個典型案例,表示式分為字首、中綴和字尾三種形式。這裡,我們通過一個四則運算的應用場景,藉助二叉樹來幫助求解表

資料結構演算法】002—Python

概念 樹 樹是一類重要的非線性資料結構,是以分支關係定義的層次結構 定義: 樹(tree)是n(n>0)個結點的有限集T,其中: 有且僅有一個特定的結點,稱為樹的根(root) 當n>1時,其餘結點可分為m(m>0)個互不相交的有限集T1,T2,……Tm,其中每一個集合本身又是一棵

資料結構例題

樹與二叉樹例題 例1 高度為K(K>=2)的完全二叉樹至少有()個葉子結點。 解: 根據二叉樹性質 二叉樹第i(i>=1)層上至多有2^(i-1)個結點 第K-1層有 2^(K-1-1)=2^(K-2) 個結點 求二叉樹至少有多

資料結構

  前幾天被面試官問到了二叉樹,因為沒有去複習所以回答的很糟糕,資料結構是大二的時候學的,在平時的web開發我能用到的機會其實不多,所以也沒有去整理,但是資料結構也是程式的靈魂架構,是需要認真研究的,故在此繼續進行整理複習。  一、什麼是樹?1.1  樹是n(n>=0)