文章目錄
一、順序儲存二叉樹
1.1 概念
從數據存儲來看,數組存儲方式和樹的存儲方式可以相互轉換
,即數組可以轉換成樹,樹也可以轉換成數組,
看下面的示意圖。
1.2 特點
- 順序二叉樹通常只考慮
完全二叉樹
:
- 第n個元素的左子節點爲 2 * n + 1
- 第n個元素的右子節點爲 2 * n + 2
- 第n個元素的父節點爲 (n-1) / 2
n : 表示二叉樹中的第幾個元素(按0開始編號如圖所示,同時對應的也是數組的順序)
複習一下完全二叉樹
:如果該二叉樹的所有葉子節點都在最後一層或者倒數第二層,而且最後一層的葉子節點在左邊連續,倒數第二層的葉子節點在右邊連續,我們稱爲 完全二叉樹
。
1.3 關係
樹的存儲方式可以與數組的方式可以互換,我看出來的一個規律,二叉樹的層次遍歷就是數組的儲存方式。
如果要將二叉樹轉爲數組存儲,既可以層次遍歷然後儲存到數組中即可。
1.4 案例
- 上圖的二叉樹的結點,要求以數組的方式來存放 array : [1, 2, 3, 4, 5, 6, 7]
- 要求在遍歷數組 array 時,仍然可以以
前序遍歷,中序遍歷和後序遍歷
的方式完成結點的遍歷。 - 手寫三種遍歷,與代碼進行比較
前序遍歷:1 2 4 5 3 6 7
中序遍歷:4 2 5 1 6 3 7
後序遍歷:4 5 2 6 7 3 1
1.5 代碼實現
1.5.1 ArrayBinaryTree 二叉樹類
package com.feng.ch12_tree.t2_arraybinarytree;
/*
* 編寫一個 ArrayBinaryTree, 實現順序存儲二叉樹遍歷
* 數組 和 二叉樹的 對應規律
* */
class ArrayBinaryTree {
private int[] array; // 存儲數據結點的數組
public ArrayBinaryTree(int[] array) {
this.array = array;
}
// 重載perOrder()
public void preOrder() {
this.preOrder(0);
}
public void infixOrder() {
this.infixOrder(0);
}
public void postOrder() {
this.postOrder(0);
}
/*
* 編寫一個方法,完成順序儲存二叉樹的一個前序遍歷
* @param index 數組的下標
* */
public void preOrder(int index) {
// 如果數組爲空,或者 array。length = 0
if (array == null || array.length == 0) {
System.out.println("數組爲空,不能按照二叉樹的前序遍歷");
}
// 輸出當前這個元素
System.out.print(array[index] + " ");
// 向左遞歸遍歷
if ((index * 2 + 1) < array.length) {
preOrder(2 * index + 1);
}
// 向右遞歸遍歷
if ((index * 2 + 2) < array.length) {
preOrder(2 * index + 2);
}
}
/*
* 方法,完成順序儲存二叉樹的一箇中序遍歷
* @param index 數組的下標
* */
public void infixOrder(int index){
// 如果數組爲空,或者 array。length = 0
if (array == null || array.length == 0) {
System.out.println("數組爲空,不能按照二叉樹的前序遍歷");
}
// 向左遞歸遍歷
if ((index * 2 + 1) < array.length) {
infixOrder(2 * index + 1);
}
// 輸出當前這個元素
System.out.print(array[index] + " ");
// 向右遞歸遍歷
if ((index * 2 + 2) < array.length) {
infixOrder(2 * index + 2);
}
}
/*
* 方法,完成順序儲存二叉樹的一個後序遍歷
* @param index 數組的下標
* */
public void postOrder(int index){
// 如果數組爲空,或者 array。length = 0
if (array == null || array.length == 0) {
System.out.println("數組爲空,不能按照二叉樹的前序遍歷");
}
// 向左遞歸遍歷
if ((index * 2 + 1) < array.length) {
postOrder(2 * index + 1);
}
// 向右遞歸遍歷
if ((index * 2 + 2) < array.length) {
postOrder(2 * index + 2);
}
// 輸出當前這個元素
System.out.print(array[index] + " ");
}
}
1.5.2 T2_ArrayBinaryTreeMain 測試類
package com.feng.ch12_tree.t2_arraybinarytree;
/*
* 順序儲存二叉樹
*
* 從數據存儲來看,數組存儲方式和樹的存儲方式可以相互轉換,即數組可以轉換成樹,樹也可以轉換成數組
* 對應的規律爲:
* 1、第n個元素的左子節點爲 2 * n + 1
* 2、第n個元素的右子節點爲 2 * n + 2
* 3、第n個元素的父節點爲 (n-1) / 2
* 4、注意 上面的 n : 表示二叉樹中的第幾個元素(按0開始編號)比如 根節點 的索引 n = 0;
* 也是對應數組裏的元素,下標正好也是從0開始的。
*
* 這裏將使用數組 來 儲存二叉樹,進行前序、後序、中序的排列。
*
* 擴展、思考:
* 二叉樹轉化爲數組儲存,其實就是二叉樹的 層次遍歷 後的結果。
* */
public class T2_ArrayBinaryTreeMain {
public static void main(String[] args) {
// int array[] = {1, 3, 4, 6, 8, 9, 15};
int array[] = {1, 3, 4, 6, 8, 9, 15};
ArrayBinaryTree arrayBinaryTree = new ArrayBinaryTree(array);
System.out.println("前序序遍歷:");
arrayBinaryTree.preOrder(); // 1 3 6 8 4 9 15
System.out.println();
System.out.println("中序遍歷:");
arrayBinaryTree.infixOrder(); // 6 3 8 1 9 4 15
System.out.println();
System.out.println("後序遍歷:");
arrayBinaryTree.postOrder(); // 6 8 3 9 15 4 1
}
}
1.5.3 測試結果
1.6 ** 實際應用 **
八大排序算法中的堆排序,就會使用到順序存儲二叉樹, 關於堆排序,我們放在<<樹結構實際應用>> 章節講解。
二、線索化二叉樹(學的不太好,沒深入)
2.1 先看一個問題
將數列 {1, 3, 6, 8, 10, 14 } 構建成一顆二叉樹. n+1=7, 7個空指針域
問題分析:
- 當我們對上面的二叉樹進行中序遍歷時,數列爲 {8, 3, 10, 1, 14, 6 }
- 但是 6, 8, 10, 14 這幾個節點的 左右指針,並沒有完全的利用上.
- 如果我們希望充分的利用 各個節點的左右指針, 讓各個節點可以指向自己的前後節點,怎麼辦?
- 解決方案-
線索二叉樹
2.1 線索二叉樹基本介紹
線索二叉樹基本介紹
-
n個結點的二叉鏈表中含有
n+1
【公式 2n-(n-1)=n+1】 個空指針域。利用二叉鏈表中的空指針域,存放指向該結點在某種遍歷次序下的前驅和後繼結點的指針(這種附加的指針稱爲"線索") -
這種加上了線索的二叉鏈表稱爲線索鏈表,相應的二叉樹稱爲線索二叉樹(Threaded BinaryTree)。根據線索性質的不同,線索二叉樹可分爲前序線索二叉樹、中序線索二叉樹和後序線索二叉樹三種
-
一個結點的前一個結點,稱爲
前驅結點
-
一個結點的後一個結點,稱爲
後繼結點
2.2 案例
應用案例說明:將下面的二叉樹,進行中序線索二叉樹。中序遍歷的數列爲 {8, 3, 10, 1, 14, 6}
思路分析:中序遍歷的結果:{8, 3, 10, 1, 14, 6}
說明: 當線索化二叉樹後,Node節點的 屬性 left 和 right ,有如下情況:
- left 指向的是左子樹,也可能是指向的前驅節點. 比如 ① 節點 left 指向的左子樹, 而 ⑩ 節點的 left 指向的就是
前驅節點
. - right指向的是右子樹,也可能是指向後繼節點,比如 ① 節點right 指向的是右子樹,而⑩ 節點的right 指向的是
後繼節點.
2.3 代碼實現
2.3.1 HeroNode 結點類
package com.feng.ch12_tree.t3_threadedbinarytree;
/*
* 創建 HeroNode 結點
* */
class HeroNode {
private int no;
private String name;
private HeroNode left;
private HeroNode right;
/*
* 說明
* 1. 如果leftType == 0 表示指向的是左子樹, 如果 1 則表示指向前驅結點
* 2. 如果rightType == 0 表示指向是右子樹, 如果 1表示指向後繼結點
* */
private int leftType;
private int rightType;
public HeroNode(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
public int getLeftType() {
return leftType;
}
public void setLeftType(int leftType) {
this.leftType = leftType;
}
public int getRightType() {
return rightType;
}
public void setRightType(int rightType) {
this.rightType = rightType;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HeroNode getLeft() {
return left;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
}
2.3.2 ThreadedBinaryTree 線索化二叉樹
package com.feng.ch12_tree.t3_threadedbinarytree;
/*
* 定義 ThreadedBinaryTree 實現了線索化功能的二叉樹
* */
class ThreadedBinaryTree {
private HeroNode root;
// 爲了實現線索化,需要創建要給指向當前結點的前驅結點的指針
// 在遞歸進行線索化時,pre 總是要保留前一個結點
private HeroNode pre = null;
public void setRoot(HeroNode root) {
this.root = root;
}
// 重載 threadedNodes
public void threadedNodes() {
this.threadedNodes(root);
}
/*
* 編寫對二叉樹 進行中序線索化的方法
*
* @param node 就是當前需要線索化的結點
* */
public void threadedNodes(HeroNode node) {
// node == null。不能線索化
if (node == null) {
return;
}
// (1) 先線索化左子樹
threadedNodes(node.getLeft());
// (2) 再線索化當前結點
// 2.1 先處理當前結點的前驅結點
// 以8結點來理解: 8結點的.left = null , 8結點的.leftType = 1
if (node.getLeft() == null) {
// 讓當前結點的左指針指向前驅結點
node.setLeft(pre);
// 修改當前結點的左指針的類型,指向前驅結點
node.setLeftType(1);
}
// 2.2 處理後繼節點
if (pre != null && pre.getRight() == null) {
// 前驅結點的右指針指向當前結點
pre.setRight(node);
// 修改前驅結點的右指針類型
pre.setRightType(1);
}
// !!! 每處理一個結點後,讓當前結點是下一個結點的前驅結點
pre = node;
// (3) 先線索化右子樹
threadedNodes(node.getRight());
}
/*
* 中序遍歷 線索化二叉樹的方法
* */
public void threadedInfixList() {
// 定義一個變量,儲存當前遍歷的節點,從root開始
HeroNode node = root;
if (root == null) {
System.out.println("鏈表爲空,無法遍歷");
}
while (node != null) {
// 循環的找到 leftType == 1 的結點,第一個找到就是 8 結點
// 後面隨着遍歷而變化,因爲當 leftType== 1 時。說明該結點是按照線索化
// 處理後的有效節點
while (node.getLeftType() == 0) {
node = node.getLeft();
}
// 打印當前這個結點
System.out.println(node);
// 如果當前結點的右指針指向的是後繼結點,就一直輸出
while (node.getRightType() == 1) {
// 獲取到當前結點的後繼結點
node = node.getRight();
System.out.println(node);
}
// 替換這個遍歷的節點
node = node.getRight();
}
}
/*
* 前序遍歷 線索化二叉樹的方法
* */
public void threadedPreList() {
// 定義一個變量,儲存當前遍歷的節點,從root開始
HeroNode node = root;
if (root == null) {
System.out.println("鏈表爲空,無法遍歷");
}
while (node != null) {
// 打印當前這個結點
System.out.println(node);
// 循環的找到 leftType == 1 的結點,第一個找到就是 8 結點
// 後面隨着遍歷而變化,因爲當 leftType == 1 時。說明該結點是按照線索化
// 處理後的有效節點
while (node.getLeftType() == 0) {
node = node.getLeft();
System.out.println(node);
}
// 如果當前結點的右指針指向的是後繼結點,就一直輸出
while (node.getRightType() == 1) {
// 獲取到當前結點的後繼結點
// System.out.println(node);
node = node.getRight();
}
// 替換這個遍歷的節點
node = node.getRight();
}
}
/*
* 後序遍歷 線索化二叉樹的方法
* */
public void threadedPostList() {
// 定義一個變量,儲存當前遍歷的節點,從root開始
HeroNode node = root;
if (root == null) {
System.out.println("鏈表爲空,無法遍歷");
}
while (node != null) {
// 循環的找到 leftType == 1 的結點,第一個找到就是 8 結點
// 後面隨着遍歷而變化,因爲當 leftType == 1 時。說明該結點是按照線索化
// 處理後的有效節點
while (node.getLeftType() == 0) {
node = node.getLeft();
}
// 如果當前結點的右指針指向的是後繼結點,就一直輸出
while (node.getRightType() == 1) {
// 獲取到當前結點的後繼結點
System.out.println(node);
node = node.getRight();
}
// 打印當前這個結點
System.out.println(node);
// 替換這個遍歷的節點
node = node.getRight();
}
}
}
2.3.3 T3_ThreadedBinaryTreeMain 測試類
package com.feng.ch12_tree.t3_threadedbinarytree;
/*
* 線索二叉樹
*
* 1、n個結點的二叉鏈表中含有n+1 【公式 2n-(n-1)=n+1】 個空指針域。利用二叉鏈表中的空指針域,
* 存放指向該結點在某種遍歷次序下的前驅和後繼結點的指針(這種附加的指針稱爲"線索")
* 2、這種加上了線索的二叉鏈表稱爲線索鏈表,相應的二叉樹稱爲線索二叉樹(Threaded BinaryTree)。
* 根據線索性質的不同,線索二叉樹可分爲前序線索二叉樹、中序線索二叉樹和後序線索二叉樹三種
* 3、一個結點的前一個(左子)結點,稱爲前驅結點;一個結點的後一個(右子)結點,稱爲後繼結點
*
* 一旦線索化後,原來的遍歷方式不能使用,從新編寫 遍歷方法,無需使用遞歸
*
* */
public class T3_ThreadedBinaryTreeMain {
public static void main(String[] args) {
// 測試 中序線索二叉樹的功能
HeroNode root = new HeroNode(1, "tom");
HeroNode node02 = new HeroNode(3, "jack");
HeroNode node03 = new HeroNode(6, "smith");
HeroNode node04 = new HeroNode(8, "mary");
HeroNode node05 = new HeroNode(10, "king");
HeroNode node06 = new HeroNode(14, "dim");
// 二叉樹,後面我們要遞歸創建,現在簡單處理,使用手動創建
root.setLeft(node02);
root.setRight(node03);
node02.setLeft(node04);
node02.setRight(node05);
node03.setLeft(node06);
// 測試線索化
ThreadedBinaryTree tree = new ThreadedBinaryTree();
tree.setRoot(root);
tree.threadedNodes();
// 測試 : 以10號爲結點
HeroNode leftNode05 = node05.getLeft();
HeroNode rightNode05 = node05.getRight();
System.out.println("10號結點的前驅結點是=" + leftNode05);
System.out.println("10號結點的後繼結點是=" + rightNode05);
/*
* 測試遍歷
* 這裏的遍歷 不能使用 T1_BinaryTreeMain.java 類中遍歷方法了。爲什麼的話 去看代碼即可。重新寫
* */
System.out.println("使用線索化的方法 中序遍歷 線索化二叉樹 ");
tree.threadedInfixList(); //8, 3, 10, 1, 14, 6
System.out.println();
System.out.println("使用線索化的方法 前序遍歷 線索化二叉樹 ");
tree.threadedPreList(); //1 3 8 10 6 14
System.out.println();
/*
* 這裏不對 有問題。
* */
System.out.println("使用線索化的方法 後序遍歷 線索化二叉樹 ");
tree.threadedPostList(); //8 10 3 14 6 1
System.out.println();
}
}
2.3.4 測試結果
後序線索化有點問題。。。。