线索二叉树
1. 线索二叉树简介
定义: 在二叉树的结点上加上线索的二叉树称为线索二叉树。
二叉树的线索化: 对二叉树以某种遍历方式(如先序、中序、后序或层次等)进行遍历,使其变为线索二叉树的过程称为对二叉树进行线索化。
现在我们来计算一下任意一颗二叉树有多少个空的指针域
假设有n个结点,一个结点有2个指针域(指向左右子节点),一共就有2n个结点
除了第一个结点不需要父节点指向,其余所有节点均需要指针,所以有n-1个已经使用的指针域
剩下:2*n-(n-1)=n+1
结论:任意二叉树的空余指针域为n+1
如上图所示:红线就是将二叉树线索化变成线索二叉树的过程。
通俗的讲:将普通二叉树的空余指针域都利用起来,指向其前驱结点或后继结点就变成了线索二叉树
2. 线索二叉树的实现
2.1 实现思路
首先我们需要选择线索化二叉树的方式-这里我们使用中序线索化二叉树
在遍历每个节点时,判断结点的左子节点和右子节点是否为空(也就是左右指针域)
如果左子节点为空就将其指向当前结点的前驱结点
如果右子节点为空就指向后继结点(这里的前驱、后继结点是相对于遍历方式而言)
相比普通二叉树我们增加两个标志位:leftType、rightType
2.2 实现代码
二叉树结点类:ThreadedBinaryNode.java
//中序线索二叉树
public class ThreadedBinaryNode {
//结点的值
int value;
//左节点
ThreadedBinaryNode leftNode;
//右节点
ThreadedBinaryNode rightNode;
//线索二叉树增加的两个属性
//1代表存储的是前驱、后续结点。0代表左右儿子(默认为0)
int leftType,rightType;
//初始化值
public ThreadedBinaryNode(int value){
this.value=value;
}
//设置左节点
public void setLeftNode(ThreadedBinaryNode leftNode) {
this.leftNode = leftNode;
}
//设置右节点
public void setRightNode(ThreadedBinaryNode rightNode) {
this.rightNode = rightNode;
}
}
线索二叉树类:ThreadedBinaryTree.java
增加了一个存储前驱结点的pre,主要实现中序线索化的threadedMid方法,遍历方法threadedIterate
public class ThreadedBinaryTree {
//根节点
ThreadedBinaryNode root;
//存储前序结点
ThreadedBinaryNode pre=null;
//设置根节点、获取根节点
public ThreadedBinaryNode getRoot() {
return root;
}
public void setRoot(ThreadedBinaryNode root) {
this.root = root;
}
//遍历中序化线索二叉树
public void threadedIterate(){
ThreadedBinaryNode node=root;
while(node!=null){
//先找到中序遍历最左边的结点
while(node.leftType==0){
node=node.leftNode;
}
System.out.print(node.value+" ");
//遍历右指针域指向后继基点的结点
while(node.rightType==1){
node=node.rightNode;
System.out.print(node.value+" ");
}
//替换遍历结点
node=node.rightNode;
}
}
//方便调用
public void threadedMid(){
threadedMid(root);
}
//中序线索化二叉树-将左右指针域为空的标记1,不为空(指向左右儿子)的默认为0
public void threadedMid(ThreadedBinaryNode node){
if(node==null)
return;
//递归处理左子树
threadedMid(node.leftNode);
//处理当前结点的左指针域
if(node.leftNode==null){
//将当前结点的左指针域指向前序结点
node.leftNode=pre;
//标记
node.leftType=1;
}
//处理前驱结点pre的右指针域
//因为这里拿不到当前结点的下一个结点,所以不能处理当前节点的右指针域
if(pre!=null&&pre.rightNode==null){
//如果pre的右指针为空,就指向他的后继结点-node
pre.rightNode=node;
pre.rightType=1;
}
//保存当前结点,为下一结点的pre
pre=node;
//递归处理右子树
threadedMid(node.rightNode);
}
}
测试类
public class TestThreadedTree {
public static void main(String[] args) {
//创建一颗空二叉树
ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
//创建根结点
ThreadedBinaryNode root=new ThreadedBinaryNode(1);
threadedBinaryTree.setRoot(root);
//创建根结点的左节点和右节点
ThreadedBinaryNode leftNode = new ThreadedBinaryNode(2);
ThreadedBinaryNode rightNode = new ThreadedBinaryNode(3);
//将左右结点连接在根结点后
root.setLeftNode(leftNode);
root.setRightNode(rightNode);
//增加四个节点 4、5、6、7方便遍历
leftNode.setLeftNode(new ThreadedBinaryNode(4));
leftNode.setRightNode(new ThreadedBinaryNode(5));
rightNode.setLeftNode(new ThreadedBinaryNode(6));
rightNode.setRightNode(new ThreadedBinaryNode(7));
//中序线索化二叉树
threadedBinaryTree.threadedMid();
//遍历中序线索二叉树
System.out.println("遍历中序线索二叉树结果为:");
threadedBinaryTree.threadedIterate();
}
}
结果:
遍历中序线索二叉树结果为:
4 2 5 1 6 3 7
3. 总结:
线索二叉树减少了的空指针域的同时又对每个节点增加了两个标志位。
好处:在遍历时不需要使用递归,相比普通二叉树遍历效率更高
在查找前驱/后继结点时更加高效
4. 应用
当路由器使用CIDR,选择下一跳的时候,或者转发分组的时候,通常会用最长前缀匹配(最佳匹配)来得到路由表的一行数据,为了更加有效的查找最长前缀匹配,通常使用的数据结构为二叉线索。