线索二叉树:
按照某种方式对二叉树进行遍历,可以把二叉树中所有结点排序为一个线性序列,在该序列中,除第一个结点外每个结点有且仅有一个直接前驱结点;除最后一个结点外每一个结点有且仅有一个直接后继结点;
在有N个节点的二叉树中需要利用N+1个空指针添加线索,这是因为在N个节点的二叉树中,每个节点有2个指针,所以一共有2N个指针,除了根节点以外,每一个节点都有一个指针从它的父节点指向它,所以一共使用了N-1个指针,所以剩下2N-(N-1)也就是N+1个空指针;
若能利用这些空指针域来存放指向该节点的直接前驱或是直接后继的指针,则可由此信息直接找到在该遍历次序下的前驱结点或后继结点,从而比递归遍历提高了遍历速度,节省了建立系统栈所使用的存储空间;
这些被重新利用起来的空指针就被称为线索(Thread),加上了这些线索的二叉树就是线索二叉树;如图:
根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种;
记node指向二叉链表中的一个结点,以下是建立线索的规则:
(1)如果node的左指针域为空,则存放指向某种遍历序列中该结点的前驱结点,这个结点称为node的前驱;
(2)如果node的右指针域为空,则存放指向中序遍历序列中该结点的后继结点。这个结点称为node的后继;
Java代码:
结点类:
package tree.thread;
public class Node
{
private int data;
private Node left;
private boolean leftIsThread; // 左孩子是否为线索
private Node right;
private boolean rightIsThread; // 右孩子是否为线索
public Node(int data)
{
this.data = data;
this.left = null;
this.leftIsThread = false;
this.right = null;
this.rightIsThread = false;
}
public int getData()
{
return data;
}
public void setData(int data)
{
this.data = data;
}
public Node getLeft()
{
return left;
}
public void setLeft(Node left)
{
this.left = left;
}
public boolean isLeftIsThread()
{
return leftIsThread;
}
public void setLeftIsThread(boolean leftIsThread)
{
this.leftIsThread = leftIsThread;
}
public Node getRight()
{
return right;
}
public void setRight(Node right)
{
this.right = right;
}
public boolean isRightIsThread()
{
return rightIsThread;
}
public void setRightIsThread(boolean rightIsThread)
{
this.rightIsThread = rightIsThread;
}
@Override
public boolean equals(Object obj)
{
if (obj instanceof Node)
{
Node temp = (Node) obj;
if (temp.getData() == this.data)
{
return true;
}
}
return false;
}
@Override
public int hashCode()
{
return super.hashCode() + this.data;
}
}
线索二叉树类:
package tree.thread;
public class ThreadTree
{
private Node root; // 根节点
private int size; // 大小
private Node pre = null; // 线索化的时候保存前驱
public ThreadTree()
{
this.root = null;
this.size = 0;
this.pre = null;
}
public ThreadTree(int[] data)
{
this.pre = null;
this.size = data.length;
this.root = createTree(data, 1); // 创建二叉树
}
/**
* 创建二叉树
*
*/
public Node createTree(int[] data, int index)
{
if (index > data.length)
{
return null;
}
Node node = new Node(data[index - 1]);
Node left = createTree(data, 2 * index);
Node right = createTree(data, 2 * index + 1);
node.setLeft(left);
node.setRight(right);
return node;
}
/**
* 将以root为根节点的二叉树线索化
*
*/
public void inThread(Node root)
{
if (root != null)
{
inThread(root.getLeft()); // 线索化左孩子
if (null == root.getLeft()) // 左孩子为空
{
root.setLeftIsThread(true); // 将左孩子设置为线索
root.setLeft(pre);
}
if (pre != null && null == pre.getRight()) // 右孩子为空
{
pre.setRightIsThread(true);
pre.setRight(root);
}
pre = root;
inThread(root.getRight()); // 线索化右孩子
}
}
/**
* 中序遍历线索二叉树
*
*/
public void inThreadList(Node root)
{
if (root != null)
{
while (root != null && !root.isLeftIsThread()) // 如果左孩子不是线索
{
root = root.getLeft();
}
do
{
System.out.print(root.getData() + ",");
if (root.isRightIsThread()) // 如果右孩子是线索
{
root = root.getRight();
}
else // 有右孩子
{
root = root.getRight();
while (root != null && !root.isLeftIsThread())
{
root = root.getLeft();
}
}
} while (root != null);
}
}
/**
* 前序遍历递归算法
*
*/
public void preList(Node root)
{
if (root != null)
{
System.out.print(root.getData() + ",");
preList(root.getLeft());
preList(root.getRight());
}
}
/**
* 中序遍历
*
*/
public void inList(Node root)
{
if (root != null)
{
inList(root.getLeft());
System.out.print(root.getData() + ",");
inList(root.getRight());
}
}
public Node getRoot()
{
return root;
}
public void setRoot(Node root)
{
this.root = root;
}
public int getSize()
{
return size;
}
public void setSize(int size)
{
this.size = size;
}
}
测试类:
package tree.thread;
public class ThreadTreeTest
{
public static void main(String[] args)
{
int[] data = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
ThreadTree threadTree = new ThreadTree(data); // 创建普通二叉树
threadTree.inList(threadTree.getRoot()); // 中序递归遍历二叉树
System.out.println();
threadTree.inThread(threadTree.getRoot()); // 采用中序遍历将二叉树线索化
threadTree.inThreadList(threadTree.getRoot()); // 中序遍历线索化二叉树
}
}
由于它充分利用了空指针域的空间(等于节省了空间),又保证了创建时的一次遍历就可以终生受用前驱后继的信息(这意味着节省了时间),所以在实际问题中,如果所使用的二叉树需要经常遍历或查找结点时需要某种遍历序列中的前驱和后继,那么采用线索二叉链表的存储结构就是不错的选择;