遍历二叉树是以一定规则将二叉树中结点排列成一个线性序列,从而得到二叉树中结点的前序序列,中序序列,后序序列。这实质上是对一个非线性结构进行线性化,使得每一个结点(除第一个和最后一个外)在这些线性序列中有且仅有一个直接前驱和直接后继。
但是,当以二叉链表作为存储结构时,只能找到结点的左,右孩子信息,而不能直接得到结点在任一序列的直接前驱和直接后继信息,这种信息只能在遍历的动态过程中获得。
为了能直接获得结点的直接前驱和直接后继这种信息,一个最简单的办法就是在每个结点上增加两个指针域,分别指示结点在任一次序遍历中得到的前驱和后继信息。显然,这样做使得链式存储结构的存储密度大大降低。
仔细想想,在有 n 个结点的二叉链表中必定存在 n+1 个空链域(即叶子结点的左右指针域),考虑使用这些空链域来存放结点的前驱和后继信息。
线索二叉树
现做如下规定:若结点有左子树,则其 lchild 域指示其左孩子,否则令 lchild 域指示其前驱;若结点右右子树,则其 rchild 域指示其右孩子,否则令 rchild 域指示其后继。为了避免混肴,向结点结构中添加两个标志域,如下:
其中:
LTag = 0 --> lchild域指示结点的左孩子
LTag = 1 --> lchild域指示结点的前驱
RTag = 0 --> rchild域指示结点的右孩子
RTag = 1 --> rchild域指示结点的后继
以这种结点结构构成的二叉链表叫做线索链表,其中指向结点前驱和后继的指针,叫做线索。加上线索的二叉树称之为线索二叉树(Threaded Binary Tree)。
如上图,其中的实线为指针(指向左,右子树),虚线为线索(指向前驱和后继)。
对二叉树以某种次序遍历使其变成线索二叉树的过程叫做线索化。
在线索二叉树上进行遍历时,只需要先找出序列中的第一个结点,然后依次找结点后继直至其后继为空时而结束遍历。
同遍历有前序遍历,中序遍历,后序遍历,线索化也有前序线索化,中序线索化,后序线索化。这里以中序线索化为例进行介绍。
如上图,树中的所有叶子结点的右链是线索,则右链域可以直接指示结点的后继;树中的所有内部结点的右链均为指针,则内部结点无法由此直接得到后继的信息,然而,根据中序遍历的规则可知,在中序遍历中结点的后继应是遍历该结点的右子树访问到的第一个结点,即右子树中最左下位置的结点。与寻找后继结点相反,在中序线索化二叉树中寻找前驱的规律是:若其左标志为“1”,则左链是线索,指示着该结点的前驱;否则遍历该结点的左子树时最后访问的结点(左子树中最右下位置的结点)为其前驱。
若二叉树需要经常遍历或者查找结点在遍历所得到的线性序列中的前驱和后继,则应采用对应的线索二叉树。
中序线索化二叉树的实现
结点类(ThreadedNode):
package util;
public class ThreadedNode {
//线索二叉树
//节点的权
public int value;
//左儿子
public ThreadedNode leftnode;
//右儿子
public ThreadedNode rightnode;
//标识指针类型
public int lefttype;
public int righttype;
public ThreadedNode(int value) {
this.value=value;
}
//设置左儿子
public void setleftnode(ThreadedNode node) {
this.leftnode=node;
}
//设置右儿子
public void setrightnode(ThreadedNode node) {
this.rightnode=node;
}
//中序遍历
public void midShow() {
//遍历左节点
if(leftnode!=null) {
leftnode.midShow();
}
//遍历当前节点的内容
System.out.print(value+" ");
//遍历右节点
if(rightnode!=null) {
rightnode.midShow();
}
}
}
中序线索二叉树类(ThreadedBinaryTree):
package other;
import util.ThreadedNode;
public class ThreadedBinaryTree {
//线索二叉树
//创建根节点
ThreadedNode root;
//用于临时存储前驱节点(线索化)
ThreadedNode pre=null;
//设置根节点
public void setRoot(ThreadedNode node) {
this.root=node;
}
//获取根节点
public ThreadedNode getRoot() {
return this.root;
}
//中序线索化二叉树
public void threadNodes() {
threadNodes(root);
}
public void threadNodes(ThreadedNode node) {
//如果当前节点为空
if(node==null) {
return;
}
//处理左子树
threadNodes(node.leftnode);
//处理当前节点
//处理当前节点的左指针
if(node.leftnode==null) {
//让当前节点的左指针指向当前节点的前驱节点
node.leftnode=pre;
//改变当前节点左指针类型
node.lefttype=1;
}
//处理前驱节点的右指针,如果前驱节点的右指针为空(没有指向右子树)
if(pre!=null&&pre.rightnode==null) {
//让前驱节点的右指针指向当前节点
pre.rightnode=node;
//改变前驱节点右指针类型
pre.righttype=1;
}
//每处理一个节点,当前节点是下一个节点的前驱节点
pre=node;
//处理右子树
threadNodes(node.rightnode);
}
//遍历线索二叉树
public void threadIterate() {
//用于存储当前遍历节点
ThreadedNode 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 midshow() {
if(root!=null)
root.midShow();
}
}
简单Test:
package classify;
import other.ThreadedBinaryTree;
import util.ThreadedNode;
public class ThreadedBinaryTreeTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
//线索二叉树
//创建一颗二叉树
ThreadedBinaryTree bintree = new ThreadedBinaryTree();
//创建一个根节点
ThreadedNode root = new ThreadedNode(1);
//把根节点赋给树
bintree.setRoot(root);
//创建一个左节点,把设置为根节点的子节点
ThreadedNode rootl = new ThreadedNode(2);
root.setleftnode(rootl);
//创建一个右节点,把设置为根节点的子节点
ThreadedNode rootr = new ThreadedNode(3);
root.setrightnode(rootr);
//为第二层的左节点创建两个子节点
ThreadedNode four = new ThreadedNode(4);
ThreadedNode five = new ThreadedNode(5);
rootl.setleftnode(four);
rootl.setrightnode(five);
//为第二层的左节点创建两个子节点
rootr.setleftnode(new ThreadedNode(6));
rootr.setrightnode(new ThreadedNode(7));
System.out.print("中序遍历:");
//中序遍历树
bintree.midshow(); // 中序遍历:4 2 5 1 6 3 7
System.out.println();
//中序线索化二叉树
bintree.threadNodes();
//获取指定节点的后继节点
System.out.print("该节点的后继节点:");
ThreadedNode afterFive = five.rightnode;
System.out.println(afterFive.value); // 该节点的后继节点:1
//遍历中序线索化二叉树
System.out.print("遍历中序线索化二叉树:");
bintree.threadIterate(); // 遍历中序线索化二叉树:4 2 5 1 6 3 7
}
}