在前面介绍二叉树的文章中提到,二叉树可以使用两种存储结构:顺序存储和链式存储,在使用链式存储时,会存在大量的空指针域,n个节点的二叉链表中含有n+1[ 2n-(n-1)=n+1 ]个空指针域,为了可以充分利用这些空指针域,引申出了线索化二叉树,将这些空指针域利用起来,提高检索效率。
上图中,紫色区域就是没有指向的空指针域,从存储空间的角度来看,这些空指针域浪费了内存资源。
从另外的角度思考,如果需要知道C节点的前驱节点和后继节点,每次都都需要进行一次遍历,是否可以提前存储C节点的前驱节点和后继节点从而省去遍历的操作从而提高效率呢?
综合以上分析,可以通过充分利用二叉链表中的空指针域,存入节点在某种遍历方式下的前驱和后继节点的指针。
这种指向前驱和后继的指针成为线索,加上线索的二叉链成为线索链表,而对应的二叉树就称为线索化二叉树(Threaded Binary Tree)
通过中序遍历到D节点,得到了D节点right指针为空,并且D节点后继节点为B,于是将D的right节点指向B节点(如上图中的①操作),而B节点right指针也为空,于是将right指针指向A节点(如上图中的②操作),以此类推,到F节点的后继节点为Null,则F的right指针指向Null。
如上图,D节点的left指针域指向Null(第⑤部操作),E节点的前驱节点是A,将E的left指针指向A节点,以此类推,F节点的left节点指向C节点。
通过线索化后,可以看出(蓝色箭头表示前驱,粉色箭头表示后继)线索化二叉树相当于是把二叉树转换成一个特殊的双向链表,这样对新增、删除以及查找节点都带来了方便,提高了效率,转换为链表后的图示如下:
线索化需要对节点的结构进行修改,修改之后的数据结构如下:
class Node
{
private int num;
private Object data; //数据域
private Node left; //左指针域
private Node right; //右指针域
/** * leftType == 0表示指向的是左子树,如果是1表示指向前驱节点 */
private int leftType;
/** * rightType == 0表示指向的是右子树,如果是1表示指向后继节点 */
private int rightType;
}
class Node
{
private int num;
private Object data;
private Node left;
private Node right;
/** * leftType == 0表示指向的是左子树,如果是1表示指向前驱节点 */
private int leftType;
/** * rightType == 0表示指向的是右子树,如果是1表示指向后继节点 */
private int rightType;
public Node(int num, Object data)
{
this.num = num;
this.data = data;
}
/** * 前序遍历 */
public void preOrder()
{
//先输出父节点信息
System.out.println(this);
//判断此节点的左节点是否为空
//如果左节点不为空并且指针类型不是前驱类型
if (this.left != null && this.leftType != 1)
{
//向左前序遍历
this.left.preOrder();
}
//判断此节点的右节点是否为空
//如果右节点不为空并且指针类型不是后继类型
if (this.right != null && this.rightType != 1)
{
//向右前序遍历
this.right.preOrder();
}
}
/** * 中序遍历线索化二叉树 */
public void infixOrder()
{
//判断此节点的左节点是否为空
if (this.left != null && this.leftType != 1)
{
//向左中序遍历
this.left.infixOrder();
}
//输出父节点信息
System.out.println(this);
//判断此节点的右节点是否为空
if (this.right != null && this.rightType != 1)
{
//向右中序遍历
this.right.infixOrder();
}
}
/** * 后序遍历 */
public void postOrder()
{
//判断此节点的左节点是否为空
if (this.left != null && this.leftType != 1)
{
//向左后序遍历
this.left.postOrder();
}
//判断此节点的右节点是否为空
if (this.right != null && this.rightType != 1)
{
//向右后序遍历
this.right.postOrder();
}
//输出父节点信息
System.out.println(this);
}
//get set方法省略
@Override
public String toString()
{
return "Node{" +
"num=" + num +
", data=" + data +
'}';
}
}
/** * 定义ThreadedBinaryTree 实现了线索化功能的二叉树 */
class ThreadedBinaryTree
{
/** * 根节点 */
private Node root;
/** * 为了实现线索化,需要创建要指向当前节点的前驱节点的指针 * 在递归进行线索化时,pre总是保留前一个节点 */
private Node pre = null;
public void setRoot(Node root)
{
this.root = root;
}
public void createBinaryTree(ArrayList<Node> list)
{
this.createBinaryTree(list,0);
}
/** * 创建二叉树 * @param list 节点列表 * @param index 用于创建的索引 * @return */
private Node createBinaryTree(ArrayList<Node> list, int index)
{
Node node = null;
if (isEmpty())
{
setRoot(list.get(0));
}
if (index < list.size())
{
node = list.get(index);
node.setLeft(createBinaryTree(list, (2*index+1)));
node.setRight(createBinaryTree(list, (2*index+2)));
}
return node;
}
public boolean isEmpty()
{
return root == null;
}
public void preThreadNodes()
{
this.preThreadNodes(root);
}
public void infixThreadNodes()
{
this.infixThreadNodes(root);
}
public void postThreadNodes()
{
this.postThreadNodes(root);
}
/** * 前序线索化二叉树 * @param node 当前需要线索化的节点 */
private void preThreadNodes(Node node)
{
//递归回溯条件
if (node == null)
{
return;
}
//遍历到叶子节点(前驱指针未利用)
if (node.getLeft() == null)
{
node.setLeft(pre);
node.setLeftType(1);
}
//pre.getLeft() != node 一定不可少
//否则在第一次回溯后到父节点后,如果不加上此判断会让pre节点后驱指针指向该父节点
//此时pre的前驱后继节点均指向该父节点,必然会发生死循环无法结束递归
if (pre != null && pre.getRight() == null && pre.getLeft() != node)
{
pre.setRight(node);
pre.setRightType(1);
}
pre = node;
if (node.getLeftType() == 0)
{
preThreadNodes(node.getLeft());
}
preThreadNodes(node.getRight());
}
/** * 中序线索化二叉树 * @param node 当前需要线索化的节点 */
private void infixThreadNodes(Node node)
{
if (node == null)
{
return;
}
//1.递归线索化左子树
infixThreadNodes(node.getLeft());
//2.线索化当前节点
//处理当前节点的前驱节点
if (node.getLeft() == null)
{
//让当前节点的左指针指向前驱节点
node.setLeft(pre);
//修改当前节点的左指针的类型
node.setLeftType(1);
}
//处理后继节点
if (pre != null && pre.getRight() == null)
{
//让前驱节点的右指针指向当前节点
pre.setRight(node);
//修改前驱节点的右指针类型
pre.setRightType(1);
}
//每处理一个节点之后让当前节点是下一个节点的前驱节点
pre = node;
//3.递归线索化右子树
infixThreadNodes(node.getRight());
}
/** * 后序线索化二叉树 * @param node 当前需要线索化的节点 */
private void postThreadNodes(Node node)
{
if (node == null)
{
return;
}
postThreadNodes(node.getLeft());
postThreadNodes(node.getRight());
if (node.getLeft() == null)
{
node.setLeft(pre);
node.setLeftType(1);
}
if ( pre!= null && pre.getRight() == null )
{
pre.setRight(node);
pre.setRightType(1);
}
pre = node;
}
/** * 非递归方式中序遍历线索化二叉树的方法 */
public void threadedInfixList()
{
Node node = root;
while (node != null)
{
//循环地找到leftType == 1的节点
while (node.getLeftType() == 0)
{
node = node.getLeft();
}
System.out.println(node);
while (node.getRightType() == 1)
{
node = node.getRight();
System.out.println(node);
}
node = node.getRight();
}
}
public void threadedTreePreList()
{
if (root != null)
{
root.preOrder();
}else
{
System.out.println("二叉树为空,无法遍历");
}
}
public void threadedTreeInfixList()
{
if (root != null)
{
root.infixOrder();
}else
{
System.out.println("二叉树为空,无法遍历");
}
}
public void threadedTreePostList()
{
if (root != null)
{
root.postOrder();
}else
{
System.out.println("二叉树为空,无法遍历");
}
}
}
以上。
如有不足或者错误欢迎评论指正。
整理不易,留个三连再走吧!
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/TaylorSwiftiiln/article/details/119799836
内容来源于网络,如有侵权,请联系作者删除!