线索化二叉树的引出
从上图中可以看出,叶子节点和某些节点如8,10,14,6的左右节点是没有连接的,并没有完全的用上,如果想充分的利用各个节点的左右指针,让各个节点指向自己的前后节点。故引入了线索二叉树的概念
线索二叉树的介绍
线索二叉树的规则:
- 根据上图,可以看出n个节点的二叉树中,会有n+1个空指针域。利用二叉树链表中的空指针域,存放指向节点在某种遍历次序下的前驱和后继节点的指针(这种指针的附加称为索引)以下以中序遍历为例。
规定;
2. 一个节点的前一个节点称为前驱节点
3. 一个节点的后一个节点称为后继节点
线索二叉树的解释
按照上图,数组中为树的中序遍历结果,
例如:
8号节点的下一个节点是3,故后继节点为3,
因为3号节点在树中已经有了左右两个子节点,故没有索引
10号节点,有两个多余的子节点,而且在数组中上一个数是10,故树中前驱节点为10,数组中下一个节点为1,故树中的后继索引为1.,
以此类推。。
代码实现线索化思路
故针对以上情况分析,实现代码的思路如下:
当线索化二叉树之后,节点的left属性和right属性有以下几种情况
- left指向左子树也可能指向前驱节点,比如1号节点left指向左子树,而10号节点的左指向了前驱节点
- right指向右子树也可能指向后继节点。如1号节点right指向右子树,而10号节点的右指向了后继节点
故引入另外两个辅助标志,来判断是左右子树还是前后驱继节点。 - 如果leftType=0,则表示指向左子节点,leftType=1,表示指向后继节点
- 如果rightType=0,则表示指向左子节点,rightType=1,表示指向后继节点
代码实现线索化遍历的思路:
根据辅助标志来判断是子树还是前驱后继节点。
综合测试代码:
package com.njupt.binaryTree;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/07/24/20:25
* @Description:
*/
public class ThreadTree {
public static void main(String[] args) {
NodePoint root = new NodePoint(1, "aa");
NodePoint node2 = new NodePoint(3, "bb");
NodePoint node3 = new NodePoint(6, "cc");
NodePoint node4 = new NodePoint(8, "dd");
NodePoint node5 = new NodePoint(10, "ee");
NodePoint node6 = new NodePoint(14, "ff");
root.left = node2;
root.right = node3;
node2.left = node4;
node2.right = node5;
node3.left = node6;
//测试中序线索化
ThreadBinaryTree threadBinaryTree = new ThreadBinaryTree();
threadBinaryTree.root = root;
threadBinaryTree.threadedNodes();
//以10号节点测试
NodePoint left = node5.left;
NodePoint right = node5.right;
System.out.println("前驱节点" + left);
System.out.println("后驱节点" + right);
System.out.println("使用线索化遍历二叉树中序遍历");
threadBinaryTree.threadedList();
}
}
//实现线索化功能的二叉树
class ThreadBinaryTree {
public NodePoint root;
//为了实现线索化,需要创建要给指向当前结点的前驱结点的指针
//在递归进行线索化时,pre 总是保留前一个结点
public NodePoint pre = null;
//重载一把threadedNodes方法
public void threadedNodes() {
this.threadedNodes(root);
}
//遍历线索化二叉树的方法
public void threadedList() {
//定义一个变量,存储当前遍历的结点,从root开始
NodePoint node = root;
while (node != null) {
//循环的找到leftType == 1的结点,第一个找到就是8结点
//后面随着遍历而变化,因为当leftType==1时,说明该结点是按照线索化
//处理后的有效结点
while (node.leftType == 0) {
node = node.left;
}
//打印当前这个结点
System.out.println(node);
//如果当前结点的右指针指向的是后继结点,就一直输出
while (node.rightType == 1) {
//获取到当前结点的后继结点
node = node.right;
System.out.println(node);
}
//替换这个遍历的结点
node = node.right;
}
}
//编写对二叉树进行中序线索化的方法
/**
* @param
*/
public void threadedNodes(NodePoint node) {
//如果node==null, 不能线索化
if (node == null) {
return;
}
//(一)先线索化左子树
threadedNodes(node.left);
//(二)线索化当前结点[有难度]
//处理当前结点的前驱结点
//以8结点来理解
//8结点的.left = null , 8结点的.leftType = 1
if (node.left == null) {
//让当前结点的左指针指向前驱结点
node.left = pre;
//修改当前结点的左指针的类型,指向前驱结点
node.leftType = 1;
}
//处理后继结点
if (pre != null && pre.right == null) {
//让前驱结点的右指针指向当前结点
pre.right = node;
//修改前驱结点的右指针类型
pre.rightType = 1;
}
//!!! 每处理一个结点后,让当前结点是下一个结点的前驱结点
pre = node;
//(三)在线索化右子树
threadedNodes(node.right);
}
}
//节点类信息
class NodePoint {
public int no;
public String name;
public NodePoint left;
public NodePoint right;
//规定:如果leftType=0,则表示指向左子节点,leftType=1,表示指向后继节点
public int leftType;
//同理。
public int rightType;
public NodePoint(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "Node1{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
测试结果:
前驱节点Node1{no=3, name='bb'}
后驱节点Node1{no=1, name='aa'}
使用线索化遍历二叉树中序遍历
Node1{no=8, name='dd'}
Node1{no=3, name='bb'}
Node1{no=10, name='ee'}
Node1{no=1, name='aa'}
Node1{no=14, name='ff'}
Node1{no=6, name='cc'}
Process finished with exit code 0
和预期一致。