先看一個問題
將數列 {1, 3, 6, 8, 10, 14 } 構建成一顆二叉樹. n+1=7
問題分析:
當我們對上面的二叉樹進行中序遍歷時,數列爲 {8, 3, 10, 1, 14,6 }
但是 6, 8, 10, 14 這幾個節點的 左右指針,並沒有完全的利用上.
如果我們希望充分的利用 各個節點的左右指針, 讓各個節點可以指向自己的前後節點,怎麼辦?
解決方案-線索二叉樹
線索二叉樹基本介紹
1.n個結點的二叉鏈表中含有n+1 【公式 2n-(n-1)=n+1】 個空指針域。利用二叉鏈表中的空指針域,存放指向該結點在某種遍歷次序下的前驅和後繼結點的指針(這種附加的指針稱爲"線索")
2.這種加上了線索的二叉鏈表稱爲線索鏈表,相應的二叉樹稱爲線索二叉樹(Threaded BinaryTree),根據線索性質的不同,線索二叉樹可分爲前序線索二叉樹、中序線索二叉樹和後序線索二叉樹三種
3.一個結點的前一個結點,稱爲前驅結點
4.一個結點的後一個結點,稱爲後繼結點
代碼實現
線索二叉樹應用案例
應用案例說明:將下面的二叉樹,進行中序線索二叉樹。中序遍歷的數列爲 {8, 3, 10, 1, 14, 6}
說明: 當線索化二叉樹後,Node節點的 屬性 left 和 right ,有如下情況:
1.left 指向的是左子樹,也可能是指向的前驅節點. 比如 ① 節點 left 指向的左子樹, 而 ⑩ 節點的 left 指向的就是前驅節點.
2.right指向的是右子樹,也可能是指向後繼節點,比如 ① 節點right 指向的是右子樹,而⑩ 節點的right 指向的是後繼節點.
實現代碼
package treeThreadeBinaryTree;
public class ThreadeBinaryTreeDemo {
public static void main(String[] args) {
//測試中序線索化二叉樹
HeroNode root = new HeroNode(1, "tom");
HeroNode node2 = new HeroNode(3, "jack");
HeroNode node3 = new HeroNode(6, "smith");
HeroNode node4 = new HeroNode(8, "mary");
HeroNode node5 = new HeroNode(10, "king");
HeroNode node6 = new HeroNode(14, "dim");
//二叉樹後面我們要遞歸創建,現在手動創建
root.setLeft(node2);
root.setRight(node3);
node2.setLeft(node4);
node2.setRight(node5);
node3.setLeft(node6);
//測試線索化
ThreadeBinarTree threadeBinarTree = new ThreadeBinarTree();
threadeBinarTree.setRoot(root);
threadeBinarTree.threadeNodes();
//測試,以10號測試
HeroNode leftNode1=node5.getLeft();
HeroNode rightNode1=node5.getRight();
System.out.println("10號結點的前驅結點爲="+leftNode1);
System.out.println("10號結點的後繼結點爲="+rightNode1);
}
}
//定義ThreadeBinarTree實現了線索化功能的二叉樹
class ThreadeBinarTree{
private HeroNode root;
//爲了實現線索化,需要創建一個當前結點的前驅結點的指針
//在遞歸進行線索化的時候,pre總是保留前一個結點
private HeroNode pre = null;
public void setRoot(HeroNode root) {
this.root = root;
}
//重載線索化的方法
public void threadeNodes() {
this.threadeNodes(root);
}
//編寫對二叉樹中序線索化的方法
/**
*
* @param node 需要線索化的結點
*/
public void threadeNodes(HeroNode node) {
//如果node == null不能線索化
if(node == null) {
return ;
}
//(一),先線索化左子樹
threadeNodes(node.getLeft());
//(二)線索化當前結點
//處理當前結點的前驅結點
if(node.getLeft() == null) {
//讓當前結點的左指針指向前驅結點
node.setLeft(pre);
//修改當前結點的左指針類型
node.setLeftType(1);
}
//處理後繼結點
if(pre !=null && pre.getRight() == null) {
//讓前驅結點的右指針指向當前結點
pre.setRight(node);
pre.setRightType(1);
}
//!!!!!!重要:每次處理一個結點後,讓當前結點指向下一個結點的前驅結點
pre = node;
//(三)線索化右子樹
threadeNodes(node.getRight());
}
//刪除結點
public void delNode(int no) {
if(this.root != null) {
//如果只有一個root,立即判斷root是不是就要刪除的結點
if(root.getNo() == no) {
root = null;
}else {
//遞歸刪除
root.delNode(no);
}
}else {
System.out.println("空樹無法刪除");
}
}
//前序遍歷
public void preOrder() {
if(this.root != null) {
this.root.preOrder();
}else {
System.out.println("二叉樹爲空,無法遍歷");
}
}
//中序遍歷
public void infixOrder() {
if(this.root != null) {
this.root.infixOrder();
}else {
System.out.println("二叉樹爲空,無法遍歷");
}
}
//後序遍歷
public void postOrder() {
if(this.root != null) {
this.root.postOrder();
}else {
System.out.println("二叉樹爲空,無法遍歷");
}
}
//調用前序遍歷查找
public HeroNode preOrdersearch(int no) {
if(root != null) {
return root.preOrdersearch(no);
}else {
return null;
}
}
//調用中序遍歷查找
public HeroNode infixOrdersearch(int no) {
if(root != null) {
return root.infixOrdersearch(no);
}else {
return null;
}
}
//調用後序遍歷查找
public HeroNode postOrdersearch(int no) {
if(root != null) {
return root.postOrdersearch(no);
}else {
return null;
}
}
}
//創建HeroNode
//先創建HeroNode結點
class HeroNode {
private int no;
private String name;
private HeroNode left;// 默認null
private HeroNode right;// 默認null
//定義兩個屬性
//規定:
//1.如果leftType==0,表示指向左子樹,如果爲1表示指向前驅結點
//2.如果rightType==0,表示指向右子樹,如果爲1表示指向後繼結點
private int leftType;
private int rightType;
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 HeroNode(int no, String name) {
super();
this.no = no;
this.name = name;
}
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;
}
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + "]";
}
// 遞歸刪除結點
// 規定
// 1.如果刪除的節點是葉子節點,則刪除該節點
// 2.如果刪除的節點是非葉子節點,則刪除該子樹
public void delNode(int no) {
/**
* 思路: 1.因爲我們的二叉樹是單向的,所以我們是判斷當前結點的子節點是否是需要刪除的結點,而不能判斷當前的結點是不是需要刪除的結點
* 2.如果當前的結點的做子節點不爲空,並且左子節點就是要刪除的結點,就將this.left = null;並且返回(遞歸刪除結束)
* 3.如果當前結點的右子節點不爲空,並且右子節點就是要刪除的結點,就愛那個this.right = null;並且就返回(結束遞歸刪除)
* 4.如果第2步和第3步沒有刪除結點,那我們就需要向左子樹遞歸刪除 5.如果第4不也沒有 刪除結點,則應當向右子樹進行遞歸刪除。
*/
// 2.如果當前的結點的做子節點不爲空,並且左子節點就是要刪除的結點,就將this.left = null;並且返回(遞歸刪除結束)
if (this.left != null && this.left.no == no) {
this.left = null;
return;
}
// 3.如果當前結點的右子節點不爲空,並且右子節點就是要刪除的結點,就愛那個this.right = null;並且就返回(結束遞歸刪除)
if (this.right != null && this.right.no == no) {
this.right = null;
return;
}
// 4.如果第2步和第3步沒有刪除結點,那我們就需要向左子樹遞歸刪除
if (this.left != null) {
this.left.delNode(no);
}
// 5.如果第4不也沒有 刪除結點,則應當向右子樹進行遞歸刪除。
if (this.right != null) {
this.right.delNode(no);
}
}
// 編寫前序遍歷方法
public void preOrder() {
System.out.println(this);// 先輸出父節點
// 遞歸向左子樹序遍遍歷
if (this.left != null) {
this.left.preOrder();
}
// 遞歸向右子樹序遍遍歷
if (this.right != null) {
this.right.preOrder();
}
}
// 中序遍歷
public void infixOrder() {
// 遞歸左子樹中序遍歷
if (this.left != null) {
this.left.infixOrder();
}
// 輸出父節點
System.out.println(this);
// 遞歸向右子樹中序遍歷
if (this.right != null) {
this.right.infixOrder();
}
}
// 後序遍歷
public void postOrder() {
// 遞歸左子樹後序遍歷
if (this.left != null) {
this.left.postOrder();
}
// 遞歸向右子樹後序遍歷
if (this.right != null) {
this.right.postOrder();
}
// 輸出父節點
System.out.println(this);
}
// 前序遍歷查找
/**
*
* @param no 要查找的no
* @return 如果找到就返回node,如果找不到就返回null
*/
public HeroNode preOrdersearch(int no) {
System.out.println("前序遍歷ing");
// 比較當前結點是不是
if (this.no == no) {
return this;
}
// 判斷當前結點的左節點是否爲空,如果不爲空,則遞歸前序查找
// 如果左遞歸前序查找找到該結點,則返回
HeroNode resNode = null;
if (this.left != null) {
resNode = this.left.preOrdersearch(no);
}
if (resNode != null) {// 說明左子樹找到
return resNode;
}
// 1.如果左遞歸前序查找找到該結點,則返回
// 2.如果左遞歸前序查找找到該結點,則返回,
// 否則繼續判斷當前節點的右子節點是否爲空,如果不爲空,則繼續向右遞歸前序查找
if (this.right != null) {
resNode = this.right.preOrdersearch(no);
}
return resNode;
}
// 中序遍歷查找
public HeroNode infixOrdersearch(int no) {
// 則判斷當前結點的左節點是否爲空,如果不爲空,則遞歸中序查找
HeroNode resNode = null;
if (this.left != null) {
resNode = this.left.infixOrdersearch(no);
}
if (resNode != null) {
return resNode;
}
// 如果找到就返回,如果沒有找到就和當前結點作比較,如果相等則返回當前結點,
System.out.println("中序遍歷ing");
if (this.no == no) {
return this;
}
// 否則繼續右遞歸中序
if (this.right != null) {
resNode = this.right.infixOrdersearch(no);
}
return resNode;
}
// 後序遍歷查找
public HeroNode postOrdersearch(int no) {
// .判斷當前結點的左節點是否爲空,如果不爲空,則遞歸後序查找
HeroNode resNode = null;
if (this.left != null) {
resNode = this.left.postOrdersearch(no);
}
if (resNode != null) {
return resNode;
}
// 如果左子樹沒有找到,繼續向右子樹遞歸後序查找
if (this.right != null) {
resNode = this.right.postOrdersearch(no);
}
if (resNode != null) {
return resNode;
}
// 如果左右子樹都沒有找到,比較當前結點是不是
System.out.println("後序遍歷ing");
if (this.no == no) {
return this;
}
return resNode;
}
}