Thread Tree(线索二叉树)
- 定义:在二叉树的结点上加上线索的二叉树称为线索二叉树,对二叉树以某种遍历方式(如先序、中序、后序或层次等)进行遍历,使其变为线索二叉树的过程称为对二叉树进行线索化。
- 二叉树的遍历本质上是将一个复杂的非线性结构转换为线性结构,使每个结点都有了唯一前驱和后继(第一个结点无前驱,最后一个结点无后继)。对于二叉树的一个结点,查找其左右子女是方便的,其前驱后继只有在遍历中得到。为了容易找到前驱和后继,有两种方法。一是在结点结构中增加向前和向后的指针,这种方法增加了存储开销,不可取;二是利用二叉树的空链(左右孩子的)指针。
- 结构:线索二叉树中的线索能记录每个结点前驱和后继信息。为了区别线索指针和孩子指针,在每个结点中设置两个标志ltag和rtag。
当tag和rtag为0时,leftChild和rightChild分别是指向左孩子和右孩子的指针;否则,leftChild是指向结点前驱的线索(pre),rightChild是指向结点的后继线索(suc)。由于标志只占用一个二进位,每个结点所需要的存储空间节省很多。
其中:ltag=0 时lchild指向左儿子;ltag=1 时lchild指向前驱;rtag=0 时rchild指向右儿子;rtag=1 时rchild指向后继。 - java代码实现:
Node节点类:
public class TBTNode<Type> {
/**
* left:左孩子或前驱
* right:右孩子或后继
* isPre:left是否为前驱
* isSuc:right是否为后继
* data:节点自身数据
*/
private TBTNode<Type> left;
private TBTNode<Type> right;
private boolean isPre;
private boolean isSuc;
private Type data;
/**
* 两种构造方法
*/
public TBTNode(){}
public TBTNode(Type data){
this.data = data;
}
/**
* 五种属性的 get() 方法
*/
public TBTNode<Type> getLeft() {
return left;
}
public TBTNode<Type> getRight() {
return right;
}
public Type getData() {
return data;
}
public boolean isPre() {
return isPre;
}
public boolean isSuc() {
return isSuc;
}
public void setLeft(TBTNode<Type> left) {
this.left = left;
}
public void setRight(TBTNode<Type> right) {
this.right = right;
}
public void setData(Type data) {
this.data = data;
}
public void setPre(boolean pre) {
isPre = pre;
}
public void setSuc(boolean suc) {
isSuc = suc;
}
}
中序线索二叉树
public class ThreadBinaryTree<Type> {
private TBTNode<Type> root;
private TBTNode<Type> previous;
/**
* 两种构造方法
*/
public ThreadBinaryTree(){//空构造方法
}
public ThreadBinaryTree(TBTNode<Type> root){//给定根节点的构造方法
this.root = root;
}
/**
* root 的 get() 和 set() 方法
*/
public TBTNode<Type> getRoot() {
return root;
}
public void setRoot(TBTNode<Type> root) {
this.root = root;
}
/**
* 判断线索二叉树是否为空树
*/
public boolean isEmpty(){
return (root == null);
}
/**
* 清空线索二叉树
*/
public void clear(){
root = null;
}
/**
* 向指定节点添加
* @param parent
* @param data
* @return
*/
public boolean add(TBTNode<Type> parent, Type data){
TBTNode temp;
if (!parent.isPre() && !parent.isSuc() //left和right都是孩子,而且孩子都存在
&& (parent.getLeft() != null) && (parent.getRight() != null))
return false;//添加节点失败
TBTNode<Type> newNode = new TBTNode<>(data);
if (parent.isPre()){//node的left是前驱
temp = parent.getLeft();//保存前驱引用
parent.setLeft(newNode);//设置node的left为新节点
parent.setPre(false);//设置node的前驱为false
newNode.setLeft(temp);//设置新节点的left为pre
newNode.setPre(true);//设置新节点的前驱为true
}else if (parent.getLeft() == null){//左节点为空
parent.setLeft(newNode);//直接设置
parent.setPre(false);
}else if (parent.isSuc()){
temp = parent.getRight();
parent.setRight(newNode);
parent.setSuc(false);
newNode.setRight(temp);
newNode.setSuc(true);
}else {
parent.setRight(newNode);
parent.setSuc(false);
}
return true;
}
/**
* 将二叉树中序线索化
* @param :进行线索化的树
*/
public void threadingTreeByLDR(){
threadingTreeByLDR(root);
}
/**
* 将二叉树中序线索化的详细实现
* 参与变量:
* current:被线索化的当前节点
* previous:被线索化的前一个节点
* Q:为什么需要previous?
* A:设置前驱线索和后继线索的时候,有两个节点参与,这两个节点就是previous和current。
* 算法过程:
* 1.对每个节点:如果current不为空,则递归线索化current的左子树; 否则返回。
* 2.如果current没有左孩子,则current的左引用为前驱线索,故设置 current.setPre(true);
* 3.如果current没有右孩子,则current的右引用为后继线索,故设置 current.setSuc(true);
* 4.如果previous不为空,且previous的右引用为后继线索,则设置previous的右引用为current;
* 5.如果current的左引用为前驱线索,则设置current的左引用为previous。
* 算法完。
* @param current 根节点
*/
private void threadingTreeByLDR(TBTNode<Type> current){
if (current != null){
threadingTreeByLDR(current.getLeft());//递归线索化左子树
if (null == current.getLeft())//current 的左引用为空
current.setPre(true);//设置 current 的左引用为前驱线索
else
current.setPre(false);//否则设置 current 的左引用为左孩子
if(null == current.getRight())//current 的右引用为空
current.setSuc(true);//设置 current 的右引用为后继线索
else
current.setSuc(false);//否则设置 current 的右引用为右孩子
if (null != previous){//previous 不为空
if (previous.isSuc()) //previous 的右引用是后继线索
previous.setRight(current);//设置 previous 的后继线索为 current
if (current.isPre())//root 的左引用为前驱线索
current.setLeft(previous);//将 previous 设置为 current 的前驱线索
}
previous = current;//将 previous 设置为 current
threadingTreeByLDR(current.getRight());//递归线索化右子树
}
}
public int height(){
return height(root);
}
/**
* 计算线索二叉树的高度
* @param current:当前线索二叉树的根节点
* @return 线索二叉树的高度
*/
private int height(TBTNode current){
if (current == null)//中序线索化时,最左下角的节点左引用为null,最右下角的节点右引用为null,必须要注意
return 0;//为空的地方不能算入
if (current.isPre() || current.isSuc())//用于判断是不是叶子节点,叶子节点也必须算一个高度
return 1;
int h1 = height(current.getLeft());
int h2 = height(current.getRight());
return (h1 > h2) ? (h1 + 1):(h2 + 1);
}
}