前言
- 二叉樹是一種特殊的樹結構,應用廣泛
- 下面,我將詳細介紹 二叉樹的相關知識,希望你們會喜歡。
目錄
1. 簡介
2. 性質
3. 存儲結構
二叉樹的存儲結構包括:順序存儲結構 & 鏈式存儲結構
注:上述的鏈式存儲方式,即爲樹結構中的孩子兄弟表示法。具體如下:
大多數情況下,二叉樹的建立會採用 鏈式存儲結構
4. 二叉樹的建立
建立的核心:
數據結構 = 鏈表 、實現方式 = 遞歸 / 非遞歸 算法
4.1 數據結構
採用鏈表的方式,也稱爲:二叉鏈表
- 爲了確保每個結點都有左右孩子,所以空指針 = 虛結點 = #
- 這種處理也稱:擴展二叉樹
- 節點結構 & 樹的定義如下
/**
* 設置結點結構
*/
public static class TreeNode<T> {
T val; // 二叉樹的結點數據
TreeNode<T> leftNode; // 二叉樹的左子樹(左孩子)
TreeNode<T> rightNode; // 二叉樹的右子樹(右孩子)
public TreeNode(T data,TreeNode<T> left,TreeNode<T> right) {
this.val = data;
this.leftNode = left;
this.rightNode = right;
}
// 獲得 & 設置二叉樹的結點數據
public T getData(){
return val;
}
public void setData(T data){
this.val = data;
}
// 獲得 & 設置二叉樹的左子樹(左孩子)
public TreeNode getLeftNode(){
return leftNode;
}
public void setLeftNode(TreeNode leftNode){
this.leftNode = leftNode;
}
// 獲得 & 設置二叉樹的右子樹(右孩子)
public TreeNode getRightNode(){
return rightNode;
}
public void setRightNode(TreeNode rightNode){
this.rightNode = rightNode;
}
}
/**
* 作用:構造二叉樹
* 注:必須逆序建立,即:先建立子節點,再逆序往上建立
* 原因:非葉子節點會使用到下面的節點,而初始化是按順序初始化的,不逆序建立會報錯
*/
public Node init(){
// 結構如下:(由下往上建立)
// A
// B C
// D E F
// G H I
Node I = new Node("I", null, null);
Node H = new Node("H", null, null);
Node G = new Node("G", null, null);
Node F = new Node("F", null, null);
Node E = new Node("E", null, I);
Node D = new Node("D", G, H);
Node C = new Node("C", E, F);
Node B = new Node("B", D, null);
Node A = new Node("A", B, C);
return A; // 返回根節點
}
4.2 遞歸 算法
- 通過 遞歸方式 構造出整個二叉樹
- 構造過程 = 將遍歷算法的輸出結點操作 替換成: 生成結點 & 賦值操作 即可
關於遍歷算法,下節會詳細說明
5. 二叉樹的遍歷
5.1 定義
從根節點出發,按照某種次序訪問二叉樹中的所有結點,使得每個結點被訪問1次 且 只被訪問1次
5.2 遍歷方式
二叉樹的遍歷方式包括:
- 前序遍歷(深度優先遍歷)
- 中序遍歷
- 後序遍歷
- 層序遍歷(廣度優先遍歷)
5.3 遍歷實現
遍歷的實現方式分爲:遞歸 & 非遞歸方式,下面會詳細說明
5.3.1 前序遍歷
也稱 深度優先遍歷
- 簡介
- 遞歸實現
/**
* 內容:前序遍歷
* 方式:遞歸
*/
public void preOrder(Node root){
// 1. 判斷二叉樹結點是否爲空;若是,則返回空操作
if(root ==null)
return;
// 2. 訪問根節點(顯示根結點)
printNode(root);
// 3. 遍歷左子樹
preOrder(root.getLeftNode());
// 4. 遍歷右子樹
preOrder(root.getRightNode());
}
- 非遞歸實現
主要採用 棧實現
/**
* 方式:非遞歸(棧實現)
*/
public static void preOrder_stack(Node root){
Stack<Node> stack = new Stack<Node>();
// 步驟1:直到當前結點爲空 & 棧空時,循環結束
while(root != null || stack.size()>0){
// 步驟2:判斷當前結點是否爲空
// a. 若不爲空,執行3
// b. 若爲空,執行5
if(root != null){
// 步驟3:輸出當前節點,並將其入棧
printNode(root);
stack.push(root);
// 步驟4:置當前結點的左孩子爲當前節點
// 返回步驟1
root = root.getLeftNode();
}else{
// 步驟5:出棧棧頂結點
root = stack.pop();
// 步驟6:置當前結點的右孩子爲當前節點
root = root.getRightNode();
// 返回步驟1
}
}
}
5.3.2 中序遍歷
- 簡介
- 遞歸實現
/**
* 方式:遞歸
*/
public void InOrder(Node root){
// 1. 判斷二叉樹結點是否爲空;若是,則返回空操作
if(root ==null)
return;
// 2. 遍歷左子樹
InOrder(root.getLeftNode());
// 3. 訪問根節點(顯示根結點)
printNode(root);
// 4. 遍歷右子樹
InOrder(root.getRightNode());
}
- 非遞歸實現
主要採用 棧實現
/**
* 方式:非遞歸(棧實現)
*/
public static void InOrder_stack(Node root){
Stack<Node> stack = new Stack<Node>();
// 1. 直到當前結點爲空 & 棧空時,循環結束
while(root != null || stack.size()>0){
// 2. 判斷當前結點是否爲空
// a. 若不爲空,執行3、4
// b. 若爲空,執行5、6
if(root != null){
// 3. 入棧當前結點
stack.push(root);
// 4. 置當前結點的左孩子爲當前節點
// 返回步驟1
root = root.getLeftNode();
}else{
// 5. 出棧棧頂結點
root = stack.pop();
// 6. 輸出當前節點
printNode(root);
// 7. 置當前結點的右孩子爲當前節點
root = root.getRightNode();
// 返回步驟1
}
}
5.3.3 後序遍歷
- 簡介
- 遞歸實現
/**
* 方式:遞歸
*/
public void PostOrder(Node root){
// 1. 判斷二叉樹結點是否爲空;若是,則返回空操作
if(root ==null)
return;
// 2. 遍歷左子樹
PostOrder(root.getLeftNode());
// 3. 遍歷右子樹
PostOrder(root.getRightNode());
// 4. 訪問根節點(顯示根結點)
printNode(root);
}
- 非遞歸實現
主要採用 棧實現
/**
* 方式:非遞歸(棧實現)
*/
public void PostOrder_stack(Node root){
Stack<Node> stack = new Stack<Node>();
Stack<Node> output = new Stack<Node>();
// 步驟1:直到當前結點爲空 & 棧空時,循環結束——> 步驟8
while(root != null || stack.size()>0){
// 步驟2:判斷當前結點是否爲空
// a. 若不爲空,執行3、4
// b. 若爲空,執行5、6
if(root != null){
// 步驟3:入棧當前結點到中間棧
output.push(root);
// 步驟4:入棧當前結點到普通棧
stack.push(root);
// 步驟4:置當前結點的右孩子爲當前節點
// 返回步驟1
root = root.getRightNode();
}else{
// 步驟5:出棧棧頂結點
root = stack.pop();
// 步驟6:置當前結點的右孩子爲當前節點
root = root.getLeftNode();
// 返回步驟1
}
}
// 步驟8:輸出中間棧的結點
while(output.size()>0){
printNode(output.pop());
}
}
5.3.4 層序遍歷
- 簡介
- 實現思路
非遞歸實現,採用 隊列
- 算法流程圖
/**
* 方式:非遞歸(採用隊列)
*/
public void levelTravel(Node root){
// 創建隊列
Queue<Node> q=new LinkedList<Node>();
// 1. 判斷當前結點是否爲空;若是,則返回空操作
if(root==null)
return;
// 2. 入隊當前結點
q.add(root);
// 3. 判斷當前隊列是否爲空,若爲空則跳出循環
while(!q.isEmpty()){
// 4. 出隊隊首元素
root = q.poll();
// 5. 輸出 出隊元素
printNode(root);
// 6. 若出隊元素有左孩子,則入隊其左孩子
if(root.getLeftNode()!=null) q.add(root.getLeftNode());
// 7. 若出隊元素有右孩子,則入隊其右孩子
if(root.getRightNode()!=null) q.add(root.getRightNode());
}
}
5.4 遍歷方式總結
6. 二叉樹的類型
- 上述講解的是基礎的二叉樹
- 根據不同的需求場景,二叉樹分爲許多類型,主要有:
- 下面,我將詳細講解各種二叉樹的類型
6.1 線索二叉樹
- 簡介
-
示意圖
-
特別注意
- 問:如何區別該指針 = 指向左(右)孩子 or 前驅(後繼)
- 答:增設標誌域:Ltag 和 Rtag
6.2 二叉排序樹
也稱:二叉查找樹、二叉搜索樹
-
特點
-
作用 & 應用場景
6.3 平衡二叉排序樹(AVL樹)
屬於 二叉搜索樹的一種特殊類型
- 特點
- 具體介紹
6.4 紅黑樹
屬於 二叉搜索樹的一種特殊類型
6.5 赫夫曼樹
- 簡介
- 哈夫曼樹算法
即,如何找出哈弗曼樹。具體算法請看下圖
更加詳細請看文章:http://www.cnblogs.com/mcgrady/p/3329825.html
- 哈夫曼編碼
更加詳細請看文章:http://blog.csdn.net/lfeng_coding/article/details/47782141
6.6 其他類型(特殊形態)
包括:斜樹、滿二叉樹 & 完全二叉樹
6.7 總結
7. 總結
- 本文主要講解了數據結構中的二叉樹
- 下面我將繼續對 數據結構,有興趣可以繼續關注Carson_Ho的安卓開發筆記