Java編程內功-數據結構與算法「線索化二叉樹」
作者:Java精髓
本篇繼續給大家介紹關于Java編程數據結構與算法,今天主要介紹線索化二叉樹的相關知識。
先看一個問題
將數列{1,3,6,8,10,14}構建成一顆二叉樹.n+1=7,如下圖所示

問題分析:
- 當我們對上面的二叉樹進行中序遍歷時,數列為{8,3,10,1,14,6}
- 但是6,8,10,14這幾個節點的左右指針,并沒有完全的利用上。
- 如果我們希望充分的利用各個節點的左右指針,讓各個節點指向自己的前后節點怎么辦?
- 解決方案-線索二叉樹
線索二叉樹基本介紹
- n 個節點的二叉鏈表中含有n+1【公式 2n-(n-1)=n+1】個空指針域。利用二叉鏈表中的空指針域,存放該節點在某種遍歷次序下的前驅和后繼點的指針(這種附加的指針稱為線索)。
- 這種加了線索的二叉鏈表稱為線索鏈表,相應的二叉樹稱為線索二叉樹(Threaded BinaryTree)。根據線索的性質不同,線索二叉樹可分為前序線索二叉樹、中序線索二叉樹、后序線索二叉樹三種。
- 一個節點的前一個節點,稱為前驅節點。
- 一個節點的后一個節點,稱為后繼節點。
中序線索二叉樹圖解

中序遍歷的結果{8,3,10,1,14,6}
說明:當線索化二叉樹后,Node節點的屬性left和right,有如下情況:
- left指向的值左子樹,也可能是指向的前驅節點,比如①節點left指向的左子樹,而⑩節點的left指向的就是前驅節點。
- right指向的右子樹,也可能是指向后繼節點,比如①節點right指向的是右子樹,而⑩節點的right指向的是后繼節點。
代碼案例
- package com.xie.tree.threadedbinarytree;
- public class ThreadedBinaryTreeDemo {
- 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);
- ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
- threadedBinaryTree.setRoot(root);
- threadedBinaryTree.threadedNodes();
- //測試:以10號節點測試
- HeroNode left = node5.getLeft();
- System.out.println("10號節點的前驅節點是:" + left);
- HeroNode right = node5.getRight();
- System.out.println("10號節點的后繼節點是:" + right);
- System.out.println("使用線索化的方式遍歷 線索二叉樹");
- threadedBinaryTree.threadedBinaryTreeList();
- /**
- * 10號節點的前驅節點是:HeroNode{no=3, name=jack}
- * 10號節點的后繼節點是:HeroNode{no=1, name=tom}
- * 使用線索化的方式遍歷 線索二叉樹
- * HeroNode{no=8, name=mary}
- * HeroNode{no=3, name=jack}
- * HeroNode{no=10, name=king}
- * HeroNode{no=1, name=tom}
- * HeroNode{no=14, name=dim}
- * HeroNode{no=6, name=smith}
- */
- }
- }
- //實現了線索化功能的二叉樹
- class ThreadedBinaryTree {
- private HeroNode root;
- //為了實現線索化,需要創建一個指向當前節點的前驅節點的指針
- //在遞歸進行線索化時,pre總是保留前一個節點
- private HeroNode pre;
- public void setRoot(HeroNode root) {
- this.root = root;
- }
- /**
- * 遍歷線索化二叉樹的方法。
- */
- public void threadedBinaryTreeList() {
- //定義一個變量,存儲當前遍歷的節點,從root開始
- HeroNode node = root;
- 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();
- }
- }
- /**
- * 重載threadedNodes方法
- */
- public void threadedNodes() {
- threadedNodes(root);
- }
- /**
- * 編寫對二叉樹進行線索化的方法
- *
- * @param node 當前需要線索化的節點
- */
- public void threadedNodes(HeroNode node) {
- if (node == null) {
- return;
- }
- //先線索化左子樹
- threadedNodes(node.getLeft());
- //線索化當前節點【有難度】
- //處理當前節點的前驅節點
- //以8節點來理解,8節點.left=null
- if (node.getLeft() == null) {
- //讓當前節點的左指針指向前驅節點
- node.setLeft(pre);
- //修改當前節點的左指針類型
- node.setLeftType(1);
- }
- //處理后繼節點
- if (pre != null && pre.getRight() == null) {
- //讓前驅節點的右指針指向當前節點
- pre.setRight(node);
- //修改前驅節點的右指針類型
- pre.setRightType(1);
- }
- //每處理一個節點后,讓當前節點是下一個節點的前驅節點
- pre = node;
- //再線索化右子樹
- threadedNodes(node.getRight());
- }
- }
- //創建HeroNode節點
- class HeroNode {
- static int preCount = 0;
- static int infoxCount = 0;
- static int postCount = 0;
- private int no;
- private String name;
- private HeroNode left;
- private HeroNode right;
- //0 表示指向的是左子樹,1 表示指向的是前驅節點
- private int leftType;
- //0 表示指向右子樹,1 表示指向的是后繼節點
- private int rightType;
- public HeroNode(int no, String name) {
- 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;
- }
- 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;
- }
- @Override
- public String toString() {
- return "HeroNode{" +
- "no=" + no +
- ", name=" + name +
- '}';
- }
- //前序遍歷
- 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);
- }
- //遞歸刪除節點
- //1.如果刪除的節點是葉子節點,則刪除該節點。
- //2.如果刪除的節點是非葉子節點,則刪除該樹。
- public void delNo(int no) {
- /**
- * 1.因為我們的二叉樹是單向的,所以我們是判斷當前節點的子節點是否是需要刪除的節點,而不能去判斷當前節點是否是需要刪除的節點。
- * 2.如果當前節點的左子節點不為空,并且左子節點就是需要刪除的節點,就將this.left = null;并且返回(結束遞歸)。
- * 3.如果當前節點的右子節點不為空,并且右子節點就是需要刪除的節點,將將this.right = null;并且返回(結束遞歸)。
- * 4.如果第2步和第3步沒有刪除節點,那么就要向左子樹進行遞歸刪除。
- * 5.如果第4步也沒有刪除節點,則應當向右子樹進行遞歸刪除。
- */
- if (this.left != null && this.left.no == no) {
- this.left = null;
- return;
- }
- if (this.right != null && this.right.no == no) {
- this.right = null;
- return;
- }
- if (this.left != null) {
- this.left.delNo(no);
- }
- if (this.right != null) {
- this.right.delNo(no);
- }
- }
- //前序遍歷查找
- public HeroNode preOrderSearch(int no) {
- HeroNode res = null;
- preCount++;//這里必須放在this.no == no 判斷之前,才進行實際的比較
- //若果找到,就返回
- if (this.no == no) {
- return this;
- }
- //沒有找到,向左子樹遞歸進行前序查找
- if (this.left != null) {
- res = this.left.preOrderSearch(no);
- }
- //如果res != null 就直接返回
- if (res != null) {
- return res;
- }
- //如果左子樹沒有找打,向右子樹進行前序查找
- if (this.right != null) {
- res = this.right.preOrderSearch(no);
- }
- //如果找到就返回
- if (res != null) {
- return res;
- }
- return res;
- }
- //中序遍歷查找
- public HeroNode infixOrderSearch(int no) {
- HeroNode res = null;
- if (this.left != null) {
- res = this.left.infixOrderSearch(no);
- }
- if (res != null) {
- return res;
- }
- infoxCount++;//這里必須放在this.no == no 判斷之前,才進行實際的比較
- if (this.no == no) {
- return this;
- }
- if (this.right != null) {
- res = this.right.infixOrderSearch(no);
- }
- if (res != null) {
- return res;
- }
- return res;
- }
- //后序遍歷查找
- public HeroNode postOrderSearch(int no) {
- HeroNode res = null;
- if (this.left != null) {
- res = this.left.postOrderSearch(no);
- }
- if (res != null) {
- return res;
- }
- if (this.right != null) {
- res = this.right.postOrderSearch(no);
- }
- if (res != null) {
- return res;
- }
- postCount++;//這里必須放在this.no == no 判斷之前,才進行實際的比較
- if (this.no == no) {
- return this;
- }
- return res;
- }
- }
【編輯推薦】
責任編輯:姜華
來源:
今日頭條