1、樹(Tree)
每個元素我們叫作“節點”;用來連線相鄰節點之間的關係,叫作“父子關係” 。
-
A 節點就是 B 節點的父節點,B 節點是 A 節點的子節點。
-
B、C、D 這三個節點的父節點是同一個節點,互稱爲兄弟節點。
-
沒有父節點的節點叫作根節點(E);
-
沒有子節點的節點叫作葉子節點或者葉節點(G…L)
2、二叉樹
二叉樹,顧名思義,每個節點最多有兩個“叉”,也就是兩個子節點,分別是左子節點和右子節點 。
- 除了葉子節點之外,每個節點都有左右兩個子節點,這種二叉樹就叫作滿二叉樹。
- 最後一層的葉子節點都靠左排列,其他層都是滿二叉樹,這種二叉樹叫作完全二叉樹。
2.1 存儲二叉樹
2.1.1 基於引用的二叉鏈式存儲法
每個節點有三個字段,其中一個存儲數據,另外兩個是指向左右子節點的指針 。
2.1.2 基於數組的順序存儲法
如果節點 X 存儲在數組中下標爲 i 的位置,下標爲 2 * i 的位置存儲的就是左子節點,下標爲 2 * i + 1 的位置存儲的就是右子節點 ,同理 下標爲 i/2 。
非完全二叉樹:
## 2.2 二叉樹的遍歷-
前序遍歷:對於樹中的任意節點來說,先打印這個節點,然後再打印它的左子樹,最後打印它的右子樹。
-
中序遍歷:對於樹中的任意節點來說,先打印它的左子樹,然後再打印它本身,最後打印它的右子樹。
-
後序遍歷:對於樹中的任意節點來說,先打印它的左子樹,然後再打印它的右子樹,最後打印這個節點本身。
-
二叉樹遍歷的時間複雜度是 O(n)。
//前序遍歷
void preOrder(Node root) {
if (root == null) return;
System.out.println(root.data); // 打印 root 節點
preOrder(root.left);
preOrder(root.right);
}
//中序遍歷
void inOrder(Node root) {
if (root == null) return;
inOrder(root.left);
System.out.println(root.data); // 打印 root 節點
inOrder(root.right);
}
//後序遍歷
void postOrder(Node root) {
if (root == null) return;
postOrder(root.left);
postOrder(root.right);
System.out.println(root.data); // 打印 root 節點
}
class Node {
public String data; //節點數據
public Node left; //左孩子
public Node right; //右孩子
public Node(String data) {
this.data = data;
}
}
2.3 二叉查找樹
二叉查找樹要求,在樹中的任意一個節點,其左子樹中的每個節點的值,都要小於這個節點的值,而右子樹節點的值都大於這個節點的值 。
2.3.1 查找操作
先取根節點,如果它等於我們要查找的數據,那就返回。如果要查找的數據比根節點的值小,那就在左子樹中遞歸查找;如果要查找的數據比根節點的值大,那就在右子樹中遞歸查找 。
public Node find(Node node,int data) {
Node p = node;
while (p != null) {
if (data < p.data) p = p.left;
else if (data > p.data) p = p.right;
else return p;
}
return null;
}
2.3.2 插入操作
如果要插入的數據比節點的數據大,並且節點的右子樹爲空,就將新數據直接插到右子節點的位置;如果不爲空,就再遞歸遍歷右子樹,查找插入位置。
public void insert(int data) {
if (tree == null) {
tree = new Node(data);
return;
}
Node p = tree;
while (p != null) {
if (data > p.data) {
if (p.right == null) {
p.right = new Node(data);
return;
}
p = p.right;
} else { // data < p.data
if (p.left == null) {
p.left = new Node(data);
return;
}
p = p.left;
}
}
}
2.3.3 刪除操作
-
第一種情況是,如果要刪除的節點沒有子節點;
-
第二種情況是,如果要刪除的節點只有一個子節點(只有左子節點或者右子節點);
-
第三種情況是,如果要刪除的節點有兩個子節點。
public void delete(int data) {
Node p = tree; // p 指向要刪除的節點,初始化指向根節點
Node pp = null; // pp 記錄的是 p 的父節點
while (p != null && p.data != data) {
pp = p;
if (data > p.data) p = p.right;
else p = p.left;
}
if (p == null) return; // 沒有找到
// 要刪除的節點有兩個子節點
if (p.left != null && p.right != null) { // 查找右子樹中最小節點
Node minP = p.right;
Node minPP = p; // minPP 表示 minP 的父節點
while (minP.left != null) {
minPP = minP;
minP = minP.left;
}
p.data = minP.data; // 將 minP 的數據替換到 p 中
p = minP; // 下面就變成了刪除 minP 了
pp = minPP;
}
// 刪除節點是葉子節點或者僅有一個子節點
Node child; // p 的子節點
if (p.left != null) child = p.left;
else if (p.right != null) child = p.right;
else child = null;
if (pp == null) tree = child; // 刪除的是根節點
else if (pp.left == p) pp.left = child;
else pp.right = child;
}
3、紅黑樹
- 根節點是黑色的;
- 每個葉子節點都是黑色的空節點(NIL),也就是說,葉子節點不存儲數據;
- 任何相鄰的節點都不能同時爲紅色,也就是說,紅色節點是被黑色節點隔開的;
- 每個節點,從該節點到達其可達葉子節點的所有路徑,都包含相同數目的黑色節點;
具體實現參考紅黑樹
4、遞歸樹
遞歸的思想就是,將大問題分解爲小問題來求解,然後再將小問題分解爲小小問題。這樣一層一層地分解,直到問題的數據規模被分解得足夠小,不用繼續遞歸分解爲止。
如果我們把這個一層一層的分解過程畫成圖,它其實就是一棵樹。我們給這棵樹起一個名字,叫作遞歸樹。