樹是一種很常見的分線性數據結構,公司的組織架構,行政區劃結構等都是樹形結構。樹形結構裏常見的有樹和二叉樹。node
樹是n(n>=0)個結點的有限集。算法
在任意一棵非空樹中:數組
(1)有且僅有一個特定的稱爲根(root)的結點數據結構
(2)當n>1時,其他結點可分爲m(m>0)個互不相交的有限集,其中每個集合自己又是一棵樹,稱爲根的子樹(遞歸的過程)架構
如上圖所示:ide
圖3-1是n=0的樹;優化
圖3-2是n=1只有一個根節點的樹;spa
圖3-3是一棵普通的樹,B爲根節點的樹T1 = {B,E,F,J} 是A的子樹,B爲T1的根節點,同時也有本身的子樹。3d
如圖所示的樹有如下3中表示方法:指針
其中1是集合形式看起來很清晰;2是層級表示方式,相似書的目錄;3是一種廣義表的表示方法。
結點:包含一個數據元素 及 若干指向其子樹的分支。
例如結點B,包含告終點數據B 和 指向子樹E和F的分支。
度:結點擁有的子樹數稱爲結點的度。
例如:結點B包含了兩個子樹,度爲2;結點D包含了3個子樹,度爲3.
葉子(終端結點):度爲0的結點(沒有子樹的結點)
例如:J、F、C、G、H、I都是樹的葉子。
分支結點(非終端結點):度不爲0的結點(有子樹的結點)。除了根節點外,分支結點也成爲內部結點。
例如:A、B、E、D爲分支結點;B、E、D爲內部結點。
樹的度:樹內各個結點的度的最大值。
例如:A的度爲3;B的度爲2;D的度爲3,其他結點度爲0,因此樹的度爲3。
孩子:結點的子樹稱爲結點的孩子,反過來,該節點稱爲孩子的雙親。
例如:結點A有B、C、D 3個孩子,A是B、C、D的雙親結點。
兄弟:同一雙親的孩子互爲兄弟。
祖先:從根節點到某個結點(N)經歷的全部結點稱爲該節點(N)的祖先;反之,以某結點(N)爲根的任一結點都是該節點(N)的子孫。
堂兄弟:雙親結點在同一層的結點互爲堂兄弟。
例如:E 和 G、H、I爲堂兄弟。
結點的層次:結點的層次是從根節點開始,根爲第一層,依次遞增,因此上面樹的結點A在第1層,J在第4層。若是結點在n層,其子樹(若是有子樹)就在第n+1層。
樹的深度(高度):樹種結點的最大層次稱爲樹的深度(Depth)或高度。上面樹的深度爲4。
有序樹:若是樹中結點的各子樹從左到右是有次序的(即不能互換),則次樹是有序樹;反之,則爲無序樹。
二叉樹是一種有限制的樹,每一個結點最多隻有兩顆子樹(即二叉樹中不存在度大於2的結點),而且二叉樹的子樹有左右之分,次序不能任意顛倒。
能夠簡單理解爲:二叉樹是一棵任意結點度不大於2的有序樹。
二叉樹有如下5中結構:
1:空二叉樹;
2:只有根節點的二叉樹;
3:右子樹爲空的二叉樹;
4:左右子樹均非空的二叉樹;
5:左子樹爲空的二叉樹
滿二叉樹:一棵深度爲k且有2k - 1個結點的二叉樹稱爲滿二叉樹。滿二叉樹上每一層的結點數都是最大節點數。
徹底二叉樹:深度爲k的,有n個結點的二叉樹,當且僅當其每個節點都與深度爲k的滿二叉樹中自上而下,從左往右編號徹底相同的時候,就是徹底二叉樹。
注:徹底二叉樹是效率很高的數據結構,堆是一種徹底二叉樹或者近似徹底二叉樹,因此效率極高,像十分經常使用的排序算法、Dijkstra算法、Prim算法等都要用堆才能優化,二叉排序樹的效率也要藉助平衡性來提升,而平衡性基於徹底二叉樹。
(1)二叉樹的第i層上至多有2i-1個結點(i >= 1);
(2)深度爲k的二叉樹至多有2k - 1個結點(k >= 1);
(3)對於任意一棵二叉樹T,若是其終端節點數爲n0,度爲2的結點數爲n2,則= n2 + 1
有如下3中二叉樹:
按順序存儲的時候,用一組連續的存儲空間從上至下,從左至右,將二叉樹編號 i 的元素存儲在對應1維數組的下標爲 i-1 的位置,對應的存儲結構爲:
數組裏元素爲0的表示沒有此結點,能夠看出:順序存儲結構適合於徹底二叉樹,對於非徹底二叉樹比較浪費空間,圖(3)只有四個結點對於最壞狀況下,須要的空間倒是最多的。
所以,在最壞狀況下,一個深度爲k且只有k個結點的單支樹(樹中不存在度爲2的結點)須要的存儲長度是2k - 1的一維數組。
由二叉樹的定義得知:二叉樹的結點由一個數據元素 和 分別指向左子樹、右子樹的兩個分支構成。有時候爲了方便,也能夠加個雙親結點的指針域,以下圖所示:
只含有左右子樹的結點 和 含有左右子樹和雙親指針的結點的數據結構:
只含有左右子樹指針的鏈式結構稱爲二叉鏈表;含有左右子樹指針和雙親結點指針的鏈式結構稱爲三叉鏈表。
以下2種結構的二叉樹:
由存儲結構能夠得出:有n個結點的二叉鏈表中有n+1個空鏈域。
(1)二叉鏈表少存儲了個parent指針,因此更節省內存。
(2)在二叉鏈表中查找某個元素的雙親結點須要從根節點遍歷查詢,而在三叉鏈表中能夠直接經過parent指針拿到。
二叉鏈表和三叉鏈表各有優缺點,具體使用哪一種存儲結構須要根據實際狀況來決定。
二叉樹不像線性表結構只須要從前向後遍歷便可訪問每一個元素,二叉樹每一個結點均可能有兩個分支,因此遍歷方式確定不像線性表那麼簡單。二叉樹是由若干個結點遞歸構成的,每一個結點又由根節點、左子樹和右子樹3個基本單元組成,所以遍歷二叉樹就是依次遍歷這三個部分,每一個結點都按某種方法來遍歷,遍歷完全部結點即完成了對二叉樹的遍歷過程。假如限定先左後右,假如一棵二叉樹不爲空,則有如下3種方式:
先序遍歷
(1)訪問根節點;
(2)先序遍歷左子樹;
(3)先序遍歷右子樹。
中序遍歷
(1)中序遍歷左子樹;
(2)訪問根節點;
(3)中序遍歷右子樹。
後序遍歷
(1)後序遍歷左子樹;
(2)後序遍歷右子樹;
(3)訪問根節點。
按層次遍歷
從上到下,從左往右,逐層遍歷。
對於二叉樹:
先序遍歷(中左右):A->B->D->H->I->E->J->k->C->F->L->G
中序遍歷(左中右):H->D->I->B->J->E->K->A->L->F->C->G
後續遍歷(左右中):H->I->D->J->K->E->B->L->F->G->C->A按層遍歷(上->下,左->右):A->B->C->D->E->F->G->H->I->J->K->L
用遞歸來實現前序、中序、後續遍歷:
//前序 private void prePrint(Node node ) { if (node == null) return; System.out.print(node.getVal()); prePrint(node.getLeftChild()); prePrint(node.getRightChild()); } //中序 private void middlePrint(Node node) { if (node == null) return; middlePrint(node.getLeftChild()); System.out.print(node.getVal() + "->"); middlePrint(node.getRightChild()); } //後續 private void sufPrint(Node node) { if (node == null) return; sufPrint(node.getLeftChild()); sufPrint(node.getRightChild()); System.out.println(node.getVal()); }
遍歷二叉樹是以必定規則將二叉樹中結點排列成一個線性序列,不一樣方法會獲得不一樣序列方式,如先序序列、中序序列和後序序列。這其實是對一個非線性結構進行線性化的操做,使每一個節點(除了第一個和最後一個外)在這些線性序列中有且僅有一個直接前驅和直接後繼。可是,當以二叉鏈表做爲存儲結構時候,只能找到左右孩子結點信息,不能直接獲得結點在任一序列中的前驅和後繼信息,這種信息只有在遍歷的動態過程當中才能獲得。如何保持這種線性關係呢?
(1)若是在每一個結點上增長兩個指針域prefix 和 suffix,分別表示結點在任一次序遍歷時候的前驅和後繼,雖然可用實現,可是增長的兩個指針域比較耗費空間;
(2)前面咱們知道,在有n個結點的二叉鏈表中一定有n+1個空鏈域,若是用空鏈域來存儲結點的前驅和後繼就能夠充分利用內存空間,因此只須要新增兩個標識位lTag和rTag,用來區分何時指向孩子節點,何時指向前驅(後繼),標識位是布爾類型的,比(1)裏的指針更省空間。
若是結點有左子樹,則其lchild指向其左孩子結點,不然讓lchild域指向其前驅;若結點有右子樹,則其rchild指向其右孩子,不然讓rchild指向其後繼。新增兩個標識位,結點結構爲:
其中:
lTag:0 lchild域指示結點的左孩子
1 lchild域指示結點的前驅
rTag:0 rchild域指示結點的右孩子
1 rchild域指示結點的後繼
以這種結點結構構成的二叉鏈表做爲二叉樹的存儲結構叫作線索鏈表,其中指向結點前驅和後繼的指針叫作線索,加上線索的二叉樹叫作線索二叉樹,對二叉樹以某種次序遍歷使其變爲線索二叉樹的過程叫作線索化。
由於線索化的過程是將二叉鏈表中的空指針改成指向前驅或後繼的線索,並且前驅或後繼信息是在遍歷過程當中纔有的,因此線索化即爲改變二叉鏈表空指針的過程。
下面分別是前中後序對於的線索二叉樹和線索二叉鏈表,若是給二叉鏈表增長一個head指針,那麼在給定任意一個結點均可以遍歷獲得整棵樹:
如上圖所示的二叉樹,有如下3中表示方法
假設以一組連續空間存儲樹的結點,存儲結點的同時附加存儲指示雙親的結點在鏈表裏的位置,有圖可知,方便找每一個結點的雙親,不太方便找孩子結點(須要遍歷)。
圖(b)由每一個結點分別指向本身的子樹,構成多重鏈表結構;圖(c)在圖(b)基礎上增長了雙親節點。
又稱爲二叉樹表示法或二叉鏈表表示法。鏈表裏每一個結點的兩個指針分別指向該節點的第一個孩子結點和下一個兄弟結點。
前面知道二叉樹能夠用二叉鏈表表示,上小結提到樹能夠用二叉鏈表表示,因此就能夠用二叉鏈表做爲存儲媒介將樹與二叉樹對應起來,也就是說對於一棵給定的樹能夠找到惟一的一棵二叉樹與之對應,以下圖所示:
由上節樹的二叉鏈表表示法能夠知道:任何一棵樹對應的二叉樹,其右子樹確定爲空(因爲根節點確定沒有兄弟結點)。
若是把第二棵樹根節點看做第一棵樹根節點的兄弟,那麼第二顆樹根節點就是第一棵樹的右子樹,以此類推能夠將若干棵樹構成一棵二叉樹(即森林與二叉樹對應關係),以下圖所示:
由樹的結構定義能夠引出兩種次序遍歷方法:
先根(次序)遍歷樹:先訪問樹的根節點,而後依次先根遍歷根的每顆子樹
後根(次序)遍歷樹:先依次後根遍歷每顆子樹,而後訪問根節點
對這棵樹進行先根遍歷:A B E F C D G
對這棵樹進行後根遍歷:E F B C G D A
按照森林和樹的定義,能夠推出森林的兩種遍歷方法
先序遍歷森林
若是森林非空,能夠按下面規則遍歷:
(1)訪問森林中第一棵樹的根節點
(2)先序遍歷第一棵樹中根節點的子樹森林
(3)先序遍歷除去第一棵樹以後的樹構成的森林
中序遍歷森林
若是森林非空,能夠按下面規則遍歷:
(1)中序遍歷森林中第一棵樹的根節點的子樹森林
(2)訪問第一棵樹的根節點
(3)中序遍歷除去第一棵樹以後的樹構成的森林
對上圖森林進行遍歷:
先序:A B C E D F G H I J K
中序:B E C D A G F I K J H
由森林轉化成二叉樹得知,對應的二叉樹爲
因此森林的先序和中序也就是轉化成二叉樹後,二叉樹的先序和中序遍歷。