线索二叉树
0.遗留问题(只实现中序,没有实现前序和后序)
1.为什么需要线索二叉树
答:
- 如上图所示,有些结点的左右指针并没有完全得到利用
- 为了使所有的指针都得到利用,线索二叉树是有效的解决办法。
2.线索二叉树基本介绍
- n个结点的二叉链表中含有n+1个空指针{公式2n-(n-1)=n+1;2n表示总指针域数,n-1表示已经被占用的指针数},利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针
- 增加的指针称为线索,这种增加线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树、后序线索二叉树。
- 一个结点的前一个结点称为前驱结点
- 一个结点的后一个结点称为后继结点
3.线索化后的说明
线索化之后,如何判断指针是左指针、右指针还是前驱指针、后继指针呢?
解决办法:
- 当线索化之后,Node结点的属性left和right有一些说明;
- left指向的是左子树,也可能指向前驱结点,
- right指向的是右子树,也可能是后继结点,
- 增加标志位,在代码中体现在属性上:leftType==0 表示指向的是左子树,leftType==1表示指向的是前驱结点
- rightType==0表示指向的是右子树,rightType==1表示指向的是后继结点
4.代码实现
public class ThreadBinaryTree {
public static void main(String[] args) {
//测试中序线索二叉树的功能
HeroNode root = new HeroNode(1,"tom");
HeroNode node2 = new HeroNode(3,"jack");
HeroNode node3= new HeroNode(6,"smith");
HeroNode node4 = new HeroNode(8,"mary");
HeroNode node5 = new HeroNode(10,"king");
HeroNode node6 = new HeroNode(14,"jim");
//手动创建二叉树
root.setLeft(node2);
root.setRight(node3);
node2.setLeft(node4);
node2.setRight(node5);
node3.setLeft(node6);
//测试中序线索化
TreadBinayTree treadBinayTree = new TreadBinayTree();
treadBinayTree.setRoot(root);
treadBinayTree.infixTreadNodes();
// 测试:以no==10为测试结点
HeroNode leftNode = node5.getLeft();
HeroNode rightNode = node5.getRight();
System.out.println("no=10结点的前驱结点是:" + leftNode);
System.out.println("no=10结点的后继结点是:" + rightNode);
System.out.println("使用线索化的方式遍历 中序线索遍历");
treadBinayTree.threadList();
}
}
//第二:定义TreadBinaryTree实现线索功能的二叉树
class TreadBinayTree{
private HeroNode root;//定义根结点
// 为了实现线索化,需要创建指向当前结点的前驱结点的指针
// 在递归进行线索化时,pre总是保留前一个结点
private HeroNode pre = null;//不可以少
public HeroNode getRoot() {
return root;
}
public void setRoot(HeroNode root) {
this.root = root;
}
//编写中序线索化二叉树的重载方法
public void infixTreadNodes(){
this.infixTreadNodes(root);
}
// 遍历线索化二叉树的方法
public void threadList(){
// 定义一个变量,存储当前遍历的结点,从root开始
HeroNode 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();
}
}
// 编写对二叉树进行中序线索化的方法
/**
*
* @param node 表示当前需要线索化的结点
*/
public void infixTreadNodes(HeroNode node){
// 如果node == null 不能线索化
if (node == null) {
return;
}
// 线索化左子树
infixTreadNodes(node.getLeft());
//线索化当前结点(难点)
//处理当前结点的前驱结点
if (node.getLeft() == null) { //说明当前结点的左指针没有被利用起来,于是将它之前前驱结点
// 让当前结点的左指针指向前驱结点
node.setLeft(pre);
// 修改当前结点的左指针的类型,表示指向前驱结点,而不是左子树
node.setLeftType(1);
}
//处理当前结点的后继结点(难点)
// 注意:pre!=null 没搞懂为什么?
//node.getRight() == null 写法错误
if (pre!=null && pre.getRight() == null) {
// 让当前结点的右指针指向后继结点
// 写成node.setRight(pre); 出现栈溢出
pre.setRight(node);
// 修改当前结点的右指针的类型,表示指向后继结点,而不是右子树
//node.setRightType(1);
pre.setRightType(1);
}
// 易错点:每次处理完一个结点后,让当前结点是下一个结点的前驱结点
pre = node;
// 再线索化右子树
infixTreadNodes(node.getRight());
}
}
// 第一:先创建HeroNode结点
class HeroNode{
private int no;
private String name;
private HeroNode left;
private HeroNode right;
// 说明
// leftType==0 表示指向的是左子树,leftType==1表示指向的是前驱结点
// rightType==0表示指向的是右子树,rightType==1表示指向的是后继结点
private int leftType;
private int rightType;
public HeroNode(int no, String name) {
super();
this.no = no;
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HeroNode getLeft() {
return left;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
public int getLeftType() {
return leftType;
}
public void setLeftType(int leftType) {
this.leftType = leftType;
}
public int getRightType() {
return rightType;
}
public void setRightType(int rightType) {
this.rightType = rightType;
}
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + "]";
}
}