一、樹
樹是一種非線性的數據結構,它是由n(n>=0)個有限結點組成一個具有層次關係的集合。
1.樹的基本術語
節點的度:一個節點含有的子樹的個數稱爲該節點的度; 如上圖:A的爲6
葉節點或終端節點:度爲0的節點稱爲葉節點; 如上圖:B、C、H、I…等節點爲葉節點
非終端節點或分支節點:度不爲0的節點; 如上圖:D、E、F、G…等節點爲分支節點
雙親節點或父節點:若一個節點含有子節點,則這個節點稱爲其子節點的父節點; 如上圖:A是B的父節點
孩子節點或子節點:一個節點含有的子樹的根節點稱爲該節點的子節點; 如上圖:B是A的孩子節點
兄弟節點:具有相同父節點的節點互稱爲兄弟節點; 如上圖:B、C是兄弟節點
樹的度:一棵樹中,最大的節點的度稱爲樹的度; 如上圖:樹的度爲6
節點的層次:從根開始定義起,根爲第1層,根的子節點爲第2層,以此類推;
樹的高度或深度:樹中節點的最大層次; 如上圖:樹的高度爲4
堂兄弟節點:雙親在同一層的節點互爲堂兄弟;如上圖:H、I互爲兄弟節點
節點的祖先:從根到該節點所經分支上的所有節點;如上圖:A是所有節點的祖先
子孫:以某節點爲根的子樹中任一節點都稱爲該節點的子孫。如上圖:所有節點都是A的子孫
森林:由m(m>=0)棵互不相交的樹的集合稱爲森林;
2.樹的表示
樹結構相對線性表就比較複雜了,要存儲表示起來就比較麻煩了,實際中樹有很多種表示方式,如:雙親表示法,孩子表示法、孩子兄弟表示法等等。我們這裏就簡單的瞭解其中最常用的孩子兄弟表示法。
class Node{
int value;
Node leftChild;
Node rightChild;
}
二、二叉樹
- 每個結點最多有兩棵子樹,即二叉樹不存在度大於2的結點。
- 二叉樹的子樹有左右之分,其子樹的次序不能顛倒。
1.一些特殊的二叉樹
- 滿二叉樹:一個二叉樹,如果每一個層的結點數都達到最大值,則這個二叉樹就是滿二叉樹。也就是
說,如果一個二叉樹的層數爲K,且結點總數是(2^k) -1 ,則它就是滿二叉樹。 - 完全二叉樹:完全二叉樹是效率很高的數據結構,完全二叉樹是由滿二叉樹而引出來的。對於深度爲K
的,有n個結點的二叉樹,當且僅當其每一個結點都與深度爲K的滿二叉樹中編號從1至n的結點一一對
應時稱之爲完全二叉樹。 要注意的是滿二叉樹是一種特殊的完全二叉樹。
2.鏈式存儲
二叉樹的鏈式存儲結構是指,用鏈表來表示一棵二叉樹,即用鏈來指示元素的邏輯關係。 通常的方法是鏈表中每個結點由三個域組成,數據域和左右指針域,左右指針分別用來給出該結點左孩子和右孩子所在的鏈結點的存儲地址 。
class Node {
int value;
Node left; //可以代表左孩子也可以代表左子樹
Node right;
}
3.順序存儲
順序結構存儲就是使用數組來存儲,一般使用數組只適合表示完全二叉樹,因爲不是完全二叉樹會有空間的浪費。
4.遍歷方式
4.1深度遍歷——先序遍歷、中序遍歷、後序遍歷
先序遍歷:先訪問根節點,再先序遍歷左子樹,最後先序遍歷右子樹
中序遍歷:先中序遍歷左子樹,再訪問根節點,最後中序遍歷右子樹
後序遍歷:先後序遍歷左子樹,再後續遍歷右子樹,最後訪問根節點
遞歸思想代碼演示
(1)先序遍歷
public static void preOrderTraversal(Node root){
if(root == null){
return;
}
System.out.println(root.value);
preOrderTraversal(root.left);
preOrderTraversal(root.right);
}
(2)中序遍歷
public static void inOrderTraversal(Node root){
if (root == null){
return;
}
inOrderTraversal(root.left);
System.out.println(root.value);
inOrderTraversal(root.right);
}
(3)後序遍歷
public static void postOrderTraversal(Node root){
if (root == null){
return;
}
postOrderTraversal(root.left);
postOrderTraversal(root.right);
System.out.println(root.value);
}
5.一些常規二叉樹操作
(1)計算樹的節點個數
用遍歷思想
public static int count = 0;
public static void calaCount(Node root){
if(root == null){
return;
}
count ++;
calaCount(root.left);
calaCount(root.right);}
但每次調用都要對count初始化
用匯總思想
public static int calaCount2(Node root){
if(root == null){
return 0;
}
int left = calaCount2(root.left);
int right = calaCount2(root.right);
int count2 = left + right + 1;
return count2;
}
(2)計算樹的葉子節點個數
用遍歷思想
public static int leafCount = 0;
public static void calaLeafCount(Node root){
if(root == null){
return;
}
calaLeafCount(root.left);
if(root.left == null && root.right == null){
leafCount ++;
}
calaLeafCount(root.right);
}
用匯總思想
public static int calaLeafCount2(Node root){
if (root == null){
return 0;
}
if(root.left == null && root.right == null){
return 1;
}
int leftCount = calaLeafCount2(root.left);
int rightCount = calaLeafCount2(root.right);
int LeafCount = leftCount + rightCount;
return LeafCount;
}
(3)計算樹的高度
public static int calaHeight(Node root){
if(root == null){
return 0;
}
int left = calaHeight(root.left);
int right = calaHeight(root.right);
int height = Math.max(right,left) + 1;
return height;
}
4.2層序遍歷——樹的廣度優先(隊列)
- 把根放到隊列中
- 循環直到隊列爲空
(1)把隊首節點取出
(2)把隊首節點的左右孩子放入隊列(非空孩子)
代碼演示
public static void levelOrderTraversal(Node root){
if (root == null){
return;
}
Queue<Node> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
Node front = queue.poll();
System.out.println(front.value);
if(front.left != null) {
queue.add(root.left);
}
if (front.right != null) {
queue.add(root.right);
}
}
}