线索二叉树
一.概念
在二叉树的结点上加上线索的二叉树称为线索二叉树,对二叉树以某种遍历方式(如先序、中序、后序或层次等)进行遍历,使其变为线索二叉树的过程称为对二叉树进行线索化。
对于n个结点的二叉树,在二叉链存储结构中有n+1个空链域,利用这些空链域存放在某种遍历次序下该结点的前驱结点和后继结点的指针,这些指针称为线索,加上线索的二叉树称为线索二叉树。
这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。
注意:线索链表解决了无法直接找到该结点在某种遍历序列中的前驱和后继结点的问题,解决了二叉链表找左、右孩子困难的问题。
本质
线索二叉树中的线索能记录每个结点前驱和后继信息。为了区别线索指针和孩子指针,在每个结点中设置两个标志ltag和rtag。
当tag和rtag为0时,leftChild和rightChild分别是指向左孩子和右孩子的指针;否则,leftChild是指向结点前驱的线索(pre),rightChild是指向结点的后继线索(suc)。由于标志只占用一个二进位,每个结点所需要的存储空间节省很多。
现将二叉树的结点结构重新定义如下:
lchild
|
ltag
|
data
|
rtag
|
rchild
|
其中:ltag=0 时lchild指向左儿子;ltag=1 时lchild指向前驱;rtag=0 时rchild指向右儿子;rtag=1 时rchild指向后继。
构建
建立线索二叉树,或者说对二叉树线索化,实质上就是遍历一棵二叉树。在遍历过程中,访问结点的操作是检查当前的左,右指针域是否为空,将它们改为指向前驱结点或后续结点的线索。为实现这一过程,设指针pre始终指向刚刚访问的结点,即若指针p指向当前结点,则pre指向它的前驱,以便设线索。
另外,在对一颗二叉树加线索时,必须首先申请一个头结点,建立头结点与二叉树的根结点的指向关系,对二叉树线索化后,还需建立最后一个结点与头结点之间的线索。
代码实现
创建结点
public class Node { int no; String name; Node left;//null Node right;//null int leftType;//0:左子树 1:前驱节点 int rightType;//0.右子树1.后驱节点 public Node(int no, String name) { this.no = no; this.name = name; } @Override public String toString() { return "Hero{" + "no=" + no + ", name='" + name + '\'' + '}'; } /*前序遍历的方法*/ public void preOrder(){ System.out.println(this);//输出父节点 if (this.left!=null){//如果左节点不为空,进行左递归前序遍历 this.left.preOrder(); } if (this.right!=null){//如果右节点不为空,进行右递归前序遍历 this.right.preOrder(); } } /*中序遍历的方法*/ public void infixOrder(){ if (this.left!=null){//如果左节点不为空,进行左递归中序遍历 this.left.infixOrder(); } System.out.println(this);//输出父节点 if (this.right!=null){//如果右节点不为空,进行右递归中序遍历 this.right.infixOrder(); } } /*后序遍历的方法*/ public void postOrder(){ if (this.left!=null){//如果左节点不为空,进行左递后序遍历 this.left.postOrder(); } if (this.right!=null){//如果右节点不为空,进行右递归后序遍历 this.right.postOrder(); } System.out.println(this);//输出父节点 } /*前序遍历查找*/ public Node preOrderSearch(int no){ System.out.println("进入前序"); //比较当前节点 if (this.no==no){ return this; } Node resNode = null; if (this.left!=null){//左节点是否为空,进行递归前序查找 resNode =this.left.preOrderSearch(no); } if (resNode!=null){//左子树找到 return resNode; } if (this.right!=null){ resNode = this.right.preOrderSearch(no); } return resNode; } /*中序遍历查找*/ public Node infixOrderSearch(int no){ Node resNode = null; if (this.left!=null){//左节点是否为空,进行递归前序查找 resNode =this.left.infixOrderSearch(no); } if (resNode!=null){//左子树找到 return resNode; } //比较当前节点 if (this.no==no){ return this; } if (this.right!=null){ resNode = this.right.infixOrderSearch(no); } return resNode; } /*后序遍历查找*/ public Node postOrderSearch(int no){ Node resNode = null; if (this.left!=null){//左节点是否为空,进行递归前序查找 resNode =this.left.infixOrderSearch(no); } if (resNode!=null){//左子树找到 return resNode; } if (this.right!=null){ resNode = this.right.infixOrderSearch(no); } if (resNode!=null){//左子树找到 return resNode; } /*左右子树都没有找到*/ //比较当前节点 if (this.no==no){ return this; } return resNode; } /*删除节点 * 1.如果是父节点删除子树 * 2.否:直接删除*/ public void delNode(int no){ if (this.left!=null && this.left.no==no){ this.left=null; return; } //判断删除右子树 if (this.right!=null&& this.right.no==no){ this.right= null; return; } //递归向左子树查找 if (this.left!=null){ this.left.delNode(no); } //递归右子树 if (this.right!=null){ this.right.delNode(no); } } }
创建线索化二叉树
public class ThreadedBinaryTree { //线索化 需要当前节点的前驱节点 //递归进行线索化指针保留前驱节点 private Node pre = null; private Node root; public void setRoot(Node root){ this.root=root; } /*重载*/ public void threadedNode(){ this.threadedNode(root); } /*前序遍历的方法*/ public void preOrder(){ if (this.root!=null){ this.root.preOrder(); }else { System.out.println("二叉树为空无法遍历"); } } /*中序遍历的方法*/ public void infixOrder(){ if (this.root!=null){ this.root.infixOrder(); }else { System.out.println("二叉树为空无法遍历"); } } /*后序遍历的方法*/ public void postOrder(){ if (this.root!=null){ this.root.postOrder(); }else { System.out.println("二叉树为空无法遍历"); } } /*中序线索化*/ /*** * * @param node 就是当前线索化的节点 */ public void threadedNode (Node node){ if (node==null){//如果节点为空不能进行中序线索化 return; } /*1.先线索化左子树*/ threadedNode(node.left); /*2.线索化当前节点*/ //先处理当前节点的前驱节点 if (node.left==null){ //当前节点的左指针指向前驱节点 node.left=pre; //修改当前节点的左指针的类型====>前驱节点 node.leftType=1; } //处理当前节点的后驱节点 if (pre!=null && pre.right==null){ //前驱节点的右指针指向当前节点 pre.right=node; pre.rightType=1; } pre=node;//处理依次将当前节点指向下一个前驱节点 /*3.线索右子树*/ threadedNode(node.right); } /*查找思路 * 1.先判断当前节点的no是否=要查找的 * 2.如果相等,返回当前节点 * 3.不相等 * 3.1左节点是否为空,进行递归前序查找 * * 3.2右节点是否为空,进行递归前序查找 * */ /*前序遍历查找*/ public Node preOrderSearch(int no){ if (root!=null){ return root.preOrderSearch(no); }else { return null; } } /*中序遍历查找*/ public Node infixOrderSearch(int no){ if (root!=null){ return root.infixOrderSearch(no); }else { return null; } } /*后序遍历查找*/ public Node postOrderSearch(int no){ if (root!=null){ return root.postOrderSearch(no); }else { return null; } } /*删除节点*/ public void delNode(int no){ if (root!=null){ //如果恰好为root节点清空二叉树 if (root.no ==no){ root = null; }else { root.delNode(no); } } } }
代码测试
public class ThreadedBinaryTreeDemo { public static void main(String[] args) { int[] arr ={}; /*测试中序线索二叉树*/ Node root = new Node(1,"tom"); Node node1 = new Node(3, "jack"); Node node2 = new Node(6, "smith"); Node node3 = new Node(8, "mary"); Node node4 = new Node(10, "king"); Node node5 = new Node(14,"dim"); root.left = node1; root.right=node2; node1.left= node3; node1.right=node4; node2.left=node5; ThreadedBinaryTree threadedBinaryTree =new ThreadedBinaryTree(); threadedBinaryTree.setRoot(root); threadedBinaryTree.threadedNode(); //测试10 System.out.println("10号的前驱节点是:"+node4.left); System.out.println("10号的后驱节点是:"+node4.right); } }