1、什麼是樹算法
樹是一個有限結點組成的集合。能夠用遞歸的方式來定義一棵樹:樹能夠是一個空集,若非空,則一棵樹由一個根(root)結點 r 以及 0 個或多個非空的(子)樹 T一、T二、T三、...,Tk 組成,這些子樹中的每一棵的根都被來自根 r 的一條有向的邊(edge)所鏈接。每一棵子樹的根叫作根 r 的兒子(child),而 r 是每一棵子樹的根的父親(parent)。數據結構
樹的幾個概念:spa
1)樹葉(leaf):從上述的遞歸定義能夠知道,一棵樹是由 N 個結點和 N-1 條邊組成的集合。每一個結點均可以有零個或任意多個兒子,沒有兒子的結點稱爲樹葉(leaf);指針
2)兄弟(sibling):具備相同父親的結點稱爲兄弟(sibling);code
3)路徑(path):從結點 n1 到 nk 的路徑定義爲結點 n1、n2、n3、...、nk 的一個序列,使得對於 1 <= i <= k,結點 ni 是 ni+1 的父親。這個路徑的長爲該路徑上的邊的條數,即 k-1。從每個結點到它本身有一條長爲 0 的路徑。在一棵樹中,從根到每一個結點剛好存在一條路徑。blog
4)深度(depth):對於任意結點 ni,ni 的深度爲從根到 ni 的惟一路徑的長,根的深度爲0;排序
5)高(height):對於任意結點 ni,ni 的高爲從 ni 到一片樹葉的最長路徑的長,樹的高等於它的根的高(只有一個根結點的樹的高度爲0,空樹的高度爲 -1);遞歸
6)結點的度:一個結點的兒子的個數。內存
圖 1-1 樹io
如圖所示,爲一棵樹,其中,A是根結點;B、C、D、E、F 爲 A 的兒子,A 爲它們的父親;B 和 C有相同的父親,它們是兄弟(sibling);從結點 A 到結點 M 的路徑爲 A、D、H、M;結點 B 的深度爲1;結點 D 的高度爲 3;整棵樹的高度爲 4。
2、二叉樹
二叉樹是一棵樹,其中每一個結點最多有兩個兒子,分別爲左子結點和右子結點。二叉樹的平均深度是 O(√N)。
2.1 二叉樹的實現
由於一棵二叉樹的每一個結點最多隻能有兩個子結點,故而咱們能夠用指針直接指向它們。樹結點的聲明在結構上相似於雙鏈表的聲明,在聲明中,一個結點由關鍵字(Key)和兩個指向其餘結點的指針(Left 和 Right)組成:
typedef int ElementType; struct TreeNode { ElementType Element; struct TreeNode *Left; struct TreeNode *Right; }; typedef struct TreeNode *PtrToNode; typedef PtrToNode BinaryTree;
這段代碼聲明瞭一個結構,該結構包含一個 ElementType 類型的數據,用於保存結點數據;一個 Left 指針,指向結點的左子樹;和一個 Right 指針,指向結點的右子樹。
2.2 二叉樹的遍歷
按必定的順序,依次訪問二叉樹中的全部結點的操做稱爲遍歷。有三種方式能夠對二叉樹進行遍歷,如下面這幅圖爲例進行介紹:
圖 2-1 二叉樹
1)先序遍歷:
先訪問根結點,而後遍歷左子樹,再遍歷右子樹。對圖 2-1 所示的二叉樹進行先序遍歷結果爲:A-B-D-G-H-E-C-F-I-J。 先序遍歷一棵二叉樹的C語言實現以下:
/* 先序遍歷,遞歸實現 */ void PreOrderRecursion(BinaryTree T) { if (T != NULL) { printf("%d\t", T->Element); PreOrderRecursion(T->Left); PreOrderRecursion(T->Right); } }
上述代碼用遞歸的方式實現了先序遍歷二叉樹。須要注意的是判斷條件,要時刻判斷二叉樹是否爲空,這在遞歸中尤其重要。遞歸實現先序遍歷的代碼十分簡潔且易懂。可是,遞歸調用的空間複雜度較大,當輸入規模很大時,會佔用至關多的內存,也容易形成堆棧的溢出。
可使用非遞歸的方式來實現先序遍歷,大體思想以下:首先,將根結點保存到一個數據結構裏,而後訪問左子樹,待左子樹訪問完後,取出根結點,再訪問右子樹;每訪問一個子樹以前,都將這棵子樹的根結點保存,待須要訪問其右子樹時再取出。能夠知道的是,先保存進去的結點數據後取出,是一個後入先出結構,所以使用棧來保存根結點的數據十分合適。
2)後序遍歷:
先遍歷左子樹,再遍歷右子樹,最後訪問根結點。對圖 2-1 所示的二叉樹進行後序遍歷的結果爲:G-H-D-E-B-I-J-F-C-A。
/* 後序遍歷,遞歸實現 */ void PostOrderRecursion(BinaryTree T) { if (T != NULL) { PostOrderRecursion(T->Left); PostOrderRecursion(T->Right); printf("%d\n", T->Element); } }
3)中序遍歷:
先遍歷左子樹,而後訪問根結點,再遍歷右子樹。對圖 2-1 所示的二叉樹進行中序遍歷的結果爲:G-D-G-B-E-A-C-I-F-J。
/* 中序遍歷,遞歸實現 */ void InOrderRecursion(BinaryTree T) { if(T != NULL){ InOrderRecursion(T->Left); printf("%d\n",T->Element); InOrderRecursion(T->Right); } }
3、一些特殊的二叉樹
3.1 滿二叉樹
滿二叉樹是一棵深度爲 k,且有 2^(k-1) 個結點的二叉樹,以下圖所示:
圖 2-2 滿二叉樹
這就是一個滿二叉樹,能夠看到,滿二叉樹的全部葉子都在同一層 —— 最後一層,非葉子結點的度必定爲2。在一樣深度的二叉樹中,滿二叉樹的結點數是最多的,葉子數也是最多的。
3.2 徹底二叉樹
徹底二叉樹,除了最後一層外,其他層都是滿的,且最後一層的葉子結點都幾種在樹的左邊。滿二叉樹就是徹底二叉樹的一個特例。以下圖:
圖 2-3 徹底二叉樹
3.3 二叉查找樹(二叉排序樹)
二叉查找樹是二叉樹的一種,更適合於進行查找操做。對於二叉查找樹,樹中的每一個結點 X 的左子樹中全部關鍵字的值小於 X 的關鍵字值,而它的右子樹中全部關鍵字值大於 X 的關鍵字值。這意味着,該樹的全部元素能夠以某種統一的方式進行排序。二叉查找樹的平均深度是 O(logN)。
圖 2-4 二叉查找樹
參考資料:
《算法導論 第三版》
《數據結構與算法分析--C語言描述》