先看一个问题:
将数列 {1, 3, 6, 8, 10, 14 } 构建成一颗完全二叉树.
问题分析:
1) 当我们对上面的二叉树进行中序遍历时,数列为 {8, 3, 10, 1, 6, 14 }
2) 但是 6, 8, 10, 14 这几个节点的左右指针,并没有完全的利用上.(对于完全二叉树来说如果不进行线索化将会浪费n+1个指针,n为节点个数)
3) 如果我们希望充分的利用 各个节点的左右指针, 让各个节点可以指向自己的前后节点,怎么办?
4) 解决方案- 线索二叉树
线索化二叉树的基本介绍:
1) n 个结点的二叉链表中含有 n+1 【公式 2n-(n-1)=n+1】 个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索")
2) 这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(ThreadedBinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种
3) 一个结点的前一个结点,称为 前驱结点
4) 一个结点的后一个结点,称为后继结点
中序线索化的图解:
说明: 当线索化二叉树后,Node 节点的 属性 left 和 right ,有如下情况:
1) left 指向的是左子树,也可能是指向的前驱节点. 比如 ① 节点 left 指向的左子树, 而 ⑩ 节点的 left 指向的就是前驱节点
2) right 指向的是右子树,也可能是指向后继节点,比如 ① 节点 right 指向的是右子树,而⑩ 节点的 right 指向的是后继节点.
3)所以我们需要定义变量来区分到底是指向的左子树还是前驱节点……
例如:
private int leftType;// 0表示指向左子树,1表示前驱节点 private int rightType; // 0表示指向右子树,1表示后继节点
以下代码实现前,中,后序线索化二叉树并遍历:
import java.util.ArrayList;
import java.util.List;
/**
* 实现线索化二叉树
*/
public class ThreadedBinaryTree {
private int size; //二叉树的节点个数
private HeroNode2 root;
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
//为了实现线索化,需要创建一个指向当前节点的前驱节点的指针
private HeroNode2 pre = null;
public HeroNode2 getRoot() {
return root;
}
public void setRoot(HeroNode2 root) {
this.root = root;
}
/**
* 无参的前序线索化二叉树重载方法
*/
public void prefixThreadedNodes() {
this.prefixThreadedNodes(root);
}
/**
* 前序线索化二叉树,就是按照前序遍历的顺序来线索化二叉树
*
* @param heroNode2
*/
public void prefixThreadedNodes(HeroNode2 heroNode2) {
if (heroNode2 == null) {
return;
}
//1、如果当前节点的左子节点为空,就设置leftType=1
if (heroNode2.getLeft() == null) {
//让当前节点的左指针指向前驱节点
heroNode2.setLeft(pre);
//设置leftType=1
heroNode2.setLeftType(1);
}
//2、处理后继节点
if (pre != null && pre.getRight() == null) {
//让前驱节点的右指针指向当前节点
pre.setRight(heroNode2);
pre.setRightType(1);
}
//3、 !!!!! 每处理一个节点后,让当前节点是下一个节点的前驱节点
pre = heroNode2;
//4、线索化左指针
if (pre.getLeftType() == 0) {
prefixThreadedNodes(heroNode2.getLeft());
}
//5、再线索化右指针
if (pre.getRightType() == 0) {
prefixThreadedNodes(heroNode2.getRight());
}
}
/**
* 遍历前序线索化后的二叉树
*/
public void prefixOrderThreaded() {
//定义一个变量存储遍历的节点,从root开始
HeroNode2 temp = root;
int count = 0;
//循环遍历
while (temp != null) {
while (temp.getLeftType() == 0 && temp.getRightType() == 0) {
System.out.println(temp);
count++;
temp = temp.getLeft();
}
System.out.println(temp);
count++;
while (temp.getRightType() == 1 && temp.getLeftType() == 1) {
temp = temp.getRight();
System.out.println(temp);
count++;
}
temp = temp.getLeft();
System.out.println(temp);
count++;
if (count == size) {
break;
}
}
}
/**
* 无参的中序线索化二叉树重载方法
*/
public void infixThreadedNodes() {
this.infixThreadedNodes(root);
}
/**
* 中序线索化二叉树
*
* @param heroNode2
*/
public void infixThreadedNodes(HeroNode2 heroNode2) {
if (heroNode2 == null) {
return;
}
//一、先线索化左子树
infixThreadedNodes(heroNode2.getLeft());
//二、如果当前节点的左子节点为空,就设置leftType=1
if (heroNode2.getLeft() == null) {
//让当前节点的左指针指向前驱节点
heroNode2.setLeft(pre);
//设置leftType=1
heroNode2.setLeftType(1);
}
//三、处理后继节点
if (pre != null && pre.getRight() == null) {
//让前驱节点的右指针指向当前节点
pre.setRight(heroNode2);
pre.setRightType(1);
}
//四、 !!!!! 每处理一个节点后,让当前节点是下一个节点的前驱节点
pre = heroNode2;
//五、再线索化右指针
infixThreadedNodes(heroNode2.getRight());
}
/**
* 遍历中序线索化后的二叉树
*/
public void infixOrderThreaded() {
//定义一个变量,存储当前遍历的节点,从root开始
HeroNode2 temp = root;
while (temp != null) {
//循环找出左指针指向的是前驱节点而不是左子树的节点
while (temp.getLeftType() == 0) {
temp = temp.getLeft();
}
//找到了,直接输出
System.out.println(temp);
//循环找出右指针指向的是右子树而不是后继节点的节点
while (temp.getRightType() == 1) {
temp = temp.getRight();
//输出
System.out.println(temp);
}
temp = temp.getRight();
}
}
/**
* 无参的后序线索化二叉树重载方法
*/
public void suffixThreadedNodes() {
this.suffixThreadedNodes(root);
}
/**
* 后序线索化二叉树,就是按照后序遍历的顺序来线索化二叉树
*
* @param heroNode2
*/
public void suffixThreadedNodes(HeroNode2 heroNode2) {
if (heroNode2 == null) {
return;
}
//1、线索化左指针
if (heroNode2.getLeftType() == 0) {
suffixThreadedNodes(heroNode2.getLeft());
}
//2、再线索化右指针
if (heroNode2.getRightType() == 0) {
suffixThreadedNodes(heroNode2.getRight());
}
//3、如果当前节点的左子节点为空,就设置leftType=1
if (heroNode2.getLeft() == null) {
//让当前节点的左指针指向前驱节点
heroNode2.setLeft(pre);
//设置leftType=1
heroNode2.setLeftType(1);
}
//4、处理后继节点
if (pre != null && pre.getRight() == null) {
//让前驱节点的右指针指向当前节点
pre.setRight(heroNode2);
pre.setRightType(1);
}
//5、 !!!!! 每处理一个节点后,让当前节点是下一个节点的前驱节点
pre = heroNode2;
}
/**
* 遍历后序线索化后的二叉树
*/
public void suffixOrderThreaded() {
//定义一个变量,存储当前遍历的节点,从root开始
HeroNode2 temp = root;
while (temp != null) {
//循环找出左指针指向的是前驱节点而不是左子树的节点
while (temp.getLeftType() == 0) {
temp = temp.getLeft();
}
//找到了,直接输出
System.out.println(temp);
//循环找出右指针指向的是右子树而不是后继节点的节点
while (temp.getRightType() == 1) {
temp = temp.getRight();
System.out.println(temp);
}
if (temp == root) {
//若节点是二叉树的根,则其后继节点为空
temp = null;
} else if (temp == temp.getParent().getRight() || (temp == temp.getParent().getLeft() && temp.getParent().getRight() == null)) {
// 若节点是其双亲的右孩子,或是其双亲的左孩子,且其双亲没有右子树,则其后继节点为其双亲节点
temp = temp.getParent();
} else if (temp == temp.getParent().getLeft() && temp.getParent().getRight() != null) {
// 若节点是其双亲的左孩子,而且其双亲有右子树,则其节点为双亲右子树上,按后序遍历列出的第一个节点,该节点就是temp的后继节点
List<HeroNode2> heroNode2s = new ArrayList<>();
//后序遍历右子树的部分,并存入集合中
temp.getParent().getRight().postOrderList(heroNode2s);
//取出集合中的第一个
temp = heroNode2s.get(0);
}
}
}
//前序遍历
public void preOrder() {
if (this.root != null) {
this.root.prefixOrder();
} else {
System.out.println("二叉树为空,无法遍历");
}
}
//中序遍历
public void inOrder() {
if (this.root != null) {
this.root.infixOrder();
} else {
System.out.println("二叉树为空,无法遍历");
}
}
//后序遍历
public void sufOrder() {
if (this.root != null) {
this.root.suffixOrder();
} else {
System.out.println("二叉树为空,无法遍历");
}
}
//根据no删除节点
public void dropByNO(int no) {
boolean flag;
if (this.root != null) {
flag = this.root.deleteByNO(no, this.root);
} else {
System.out.println("二叉树为空");
return;
}
if (flag) {
System.out.println("删除成功");
} else {
System.out.println("没有找到要删除的节点");
}
}
//前序查找
public void preSearch(int no) {
//接收返回值
HeroNode2 temp = null;
if (this.root != null) {
temp = this.root.prefixSearch(no);
} else {
System.out.println("二叉树为空");
}
if (temp == null) {
System.out.println("没有找到no=" + no + "的节点");
} else {
System.out.println("找到了no=" + no + "的节点:" + temp);
}
}
//中序查找
public void inSearch(int no) {
HeroNode2 temp = null;
if (this.root != null) {
temp = this.root.infixSearch(no);
} else {
System.out.println("二叉树为空");
}
if (temp == null) {
System.out.println("没有找到no=" + no + "的节点");
} else {
System.out.println("找到了no=" + no + "的节点:" + temp);
}
}
//后序查找
public void sufSearch(int no) {
HeroNode2 temp = null;
if (this.root != null) {
temp = this.root.suffixSearch(no);
} else {
System.out.println("二叉树为空");
}
if (temp == null) {
System.out.println("没有找到no=" + no + "的节点");
} else {
System.out.println("找到了no=" + no + "的节点:" + temp);
}
}
}
实现节点的创建:
import java.util.List;
public class HeroNode2 {
private Integer no;
private String name;
private HeroNode2 left;
private HeroNode2 right;
private HeroNode2 parent;
private int leftType;// 0表示指向左子树,1表示前驱节点
private int rightType; // 0表示指向右子树,1表示后继节点
public HeroNode2 getParent() {
return parent;
}
public void setParent(HeroNode2 parent) {
this.parent = parent;
}
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;
}
public HeroNode2(Integer no, String name, HeroNode2 left, HeroNode2 right) {
this.no = no;
this.name = name;
this.left = left;
this.right = right;
}
public HeroNode2(Integer no, String name) {
this.no = no;
this.name = name;
}
//前序遍历
public void prefixOrder(){
//根
System.out.println("输出当前节点:" + this); //输出父节点
//有左子节点就递归
if (this.left != null){
this.left.prefixOrder();
}
//有右子节点就递归
if (this.right != null){
this.right.prefixOrder();
}
}
//中序遍历
public void infixOrder(){
//左
if (this.left != null){
this.left.infixOrder();
}
//根
System.out.println("输出当前节点:" + this);
//右
if (this.right != null){
this.right.infixOrder();
}
}
//后序遍历
public void suffixOrder(){
//有左子节点就递归
if (this.left != null){
this.left.suffixOrder();
}
//有右子节点就递归
if (this.right != null){
this.right.suffixOrder();
}
//根
System.out.println("输出当前节点:" + this);
}
//后序遍历添加进list
public void postOrderList(List<HeroNode2> heroNode2s){
if(this.left!=null && this.leftType==0){
this.left.postOrderList(heroNode2s);
}
if(this.right!=null && this.rightType==0){
this.right.postOrderList(heroNode2s);
}
heroNode2s.add(this);
}
//前序查找
public HeroNode2 prefixSearch(int no){
//根
if (this.no == no){
//System.out.println("找到了no="+no+"的节点:" + this);
return this;
}
HeroNode2 temp = null;
//左递归
if (this.left != null){
temp = this.left.prefixSearch(no);
}
//说明左子树找到了
if (temp != null){
return temp;
}
//右递归
if (this.right != null){
temp = this.right.prefixSearch(no);
}
return temp;
}
//中序查找
public HeroNode2 infixSearch(int no){
HeroNode2 temp = null;
//左递归
if (this.left != null){
temp = this.left.infixSearch(no);
}
//说明左子树找到了
if (temp != null){
return temp;
}
//根
if (this.no == no){
//System.out.println("找到了no="+no+"的节点:" + this);
return this;
}
//右递归
if (this.right != null){
temp = this.right.infixSearch(no);
}
return temp;
}
//后序查找
public HeroNode2 suffixSearch(int no){
HeroNode2 temp = null;
//左递归
if (this.left != null){
temp = this.left.suffixSearch(no);
}
//说明左子树找到了
if (temp != null){
return temp;
}
//右递归
if (this.right != null){
temp = this.right.suffixSearch(no);
}
//根
if (this.no == no){
//System.out.println("找到了no="+no+"的节点:" + this);
return this;
}
return temp;
}
//根据no删除指定节点
//说明:1、如果删除的节点是叶子节点,那么直接删除叶子节点
//说明:2、如果要删除的节点是父节点,则直接删除整棵子树
//说明:3、如果要删除的节点是根节点,则报错,返回信息:不能删除根节点
public boolean deleteByNO(int no, HeroNode2 root){
boolean flag = false;
//如果要删除根节点,直接将整棵树删除
if (root.no == no){
throw new RuntimeException("不能删除根节点");
}
if (this.left != null && this.left.no == no){
this.left = null;
return true;
}
if (this.right != null && this.right.no == no){
this.right = null;
return true;
}
//递归左子树
if (this.left != null){
flag = this.left.deleteByNO(no,root);
}
//递归右子树
if (this.right != null){
flag = this.right.deleteByNO(no,root);
}
return flag;
}
public HeroNode2() {
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
public Integer getNo() {
return no;
}
public void setNo(Integer no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HeroNode2 getLeft() {
return left;
}
public void setLeft(HeroNode2 left) {
this.left = left;
}
public HeroNode2 getRight() {
return right;
}
public void setRight(HeroNode2 right) {
this.right = right;
}
}
测试前,中,后序线索化和遍历的代码:
/**
* 测试中序线索化二叉树
*/
public class ThreadedBinaryTreeDemo {
public static void main(String[] args) {
//创建二叉树的节点
HeroNode2 root = new HeroNode2(1,"tqf");
HeroNode2 heroNode2 = new HeroNode2(3,"tom");
HeroNode2 heroNode3 = new HeroNode2(6,"jack");
HeroNode2 heroNode4 = new HeroNode2(8,"zmj");
HeroNode2 heroNode5 = new HeroNode2(10,"tsl");
HeroNode2 heroNode6 = new HeroNode2(14,"hyj");
//将节点加入二叉树中,并指定他们之间的关系
root.setLeft(heroNode2);
root.setRight(heroNode3);
heroNode2.setParent(root);
heroNode3.setParent(root);
heroNode2.setLeft(heroNode4);
heroNode2.setRight(heroNode5);
heroNode4.setParent(heroNode2);
heroNode5.setParent(heroNode2);
heroNode3.setLeft(heroNode6);
heroNode6.setParent(heroNode3);
System.out.println("中序遍历二叉树:");
root.infixOrder(); // 8 3 10 1 14 6
System.out.println("前序遍历二叉树:");
root.prefixOrder(); // 1 3 8 10 6 14
System.out.println("后序遍历二叉树:");
root.suffixOrder(); //8 10 3 14 6 1
//测试线索化
ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
threadedBinaryTree.setSize(6);
threadedBinaryTree.setRoot(root);
/*//直接调用重载方法进行中序线索化
threadedBinaryTree.infixThreadedNodes();
//测试中序线索化后的二叉树是否满足要求
System.out.println("测试中序线索化后的二叉树是否满足要求==============================");
System.out.println("8号节点的后继节点:" + heroNode4.getRight());// 3
System.out.println("10号节点的前驱节点:" + heroNode5.getLeft()); // 3
System.out.println("10号节点的后继节点:" + heroNode5.getRight()); // 1
System.out.println("14号节点的前驱节点:" + heroNode6.getLeft()); // 1
System.out.println("14号节点的后继节点:" + heroNode6.getRight()); // 6
System.out.println("测试中序线索化后的二叉树的中序遍历结果===================================================");
threadedBinaryTree.infixOrderThreaded(); // 8 3 10 1 14 6*/
/*//直接调用重载方法进行前序线索化
threadedBinaryTree.prefixThreadedNodes();
//测试前序线索化后的二叉树是否满足要求
System.out.println("测试前序线索化后的二叉树是否满足要求==============================");
System.out.println("8号节点的前驱节点:" + heroNode4.getLeft());// 3
System.out.println("8号节点的后继节点:" + heroNode4.getRight());// 10
System.out.println("10号节点的前驱节点:" + heroNode5.getLeft()); // 8
System.out.println("10号节点的后继节点:" + heroNode5.getRight()); // 6
System.out.println("14号节点的前驱节点:" + heroNode6.getLeft()); // 6
System.out.println("6号节点的后继节点:" + heroNode3.getRight()); // 14
System.out.println("测试前序线索化后的二叉树的前序遍历结果===================================================");
threadedBinaryTree.prefixOrderThreaded(); // 1 3 8 10 6 14*/
//直接调用重载方法进行前序线索化
threadedBinaryTree.suffixThreadedNodes();
//测试前序线索化后的二叉树是否满足要求
System.out.println("测试后序线索化后的二叉树是否满足要求==============================");
System.out.println("8号节点的后继节点:" + heroNode4.getRight());// 10
System.out.println("10号节点的前驱节点:" + heroNode5.getLeft()); // 8
System.out.println("10号节点的后继节点:" + heroNode5.getRight()); // 3
System.out.println("14号节点的前驱节点:" + heroNode6.getLeft()); // 3
System.out.println("14号节点的后继节点:" + heroNode6.getRight()); // 6
System.out.println("6号节点的后继节点:" + heroNode3.getRight()); // 1
System.out.println("测试后序线索化后的二叉树的后序遍历结果===================================================");
threadedBinaryTree.suffixOrderThreaded(); // 8 10 3 14 6 1
}
}