每当诞生新的数据结构时,一定要联系它对应的某一类问题。新的算法思路,不仅仅是解决问题,还要优化问题,提高程序效率,减少程序占用空间。
下面,我们来引入一个问题,结合问题,体会什么是线索化二叉树。
将数组 arr = {1,3,6,8,10,14} 构建成一颗二叉树时,结合下图,我们可以发现6,8,10,14这几个结点的左右指针,并没有完全的利用上,造成了一定空间的浪费,为了解决这个问题,我们该怎么办呢?这时候就引入我们的线索二叉树。
线索二叉树是指利用二叉链表中某些结点的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针,解决了二叉树是单向而子结点找不到父结点的问题。其中附加的指针称为线索,对应的二叉树称为线索二叉树。
上面讲到了某种遍历次序,我们这里的遍历是指前序、中序、后序遍历,因此相应的二叉树叫做,前序线索二叉树、中序线索二叉树、后序线索二叉树。
这里有个结点个数的小规律:n个结点的二叉链表中含有n+1个空指针域。一个结点2个指针,n个结点需要n-1个指针相连(把二叉树当成线性的),剩下的指针就是空指针域:2n - (n-1) = n+1;
现在主要的问题就是怎么实现空指针指向某个结点的前驱和后继啦。我们以某个结点为例,依次类推。
根据中序遍历的结果:8,3,10,1,14,6。我们会注意到一个问题,在根节点1的位置,它的前序结点指向了左子树3,后继结点指向了右子树6,并没有指向10和14。因此,要注意node结点的left指向的可能是左子树也可能是左结点,right指向的可能是右子树也可能是右结点。
实现二叉树转为中序线索二叉树的思路:左--->父--->中
- 结合上图,创建指向当前结点的前驱结点的指针pre(创建pre是为了能够让当前结点找到它的前驱是谁,因为二叉树是单向的,子结点并不能够找到父结点是谁)
- 中序遍历,先递归线索化左子树,找到左子树结点,
- 找到后,就要处理当前结点的前驱结点,处理规则是判断它的left是否为null,如果为null,left指向前驱pre,
- 接着处理后继结点,处理规则是判断它的right是否为null,如果为null,right指向后继结点。
class HeroNode {
private int no;
private String name;
private HeroNode left; // 左结点
private HeroNode right; // 右结点
private int leftType; // 0:指向左子树,1指向前驱结点
private int rightType; // 0:指向右子树,1指向后继结点
public HeroNode(int no, String name) {
this.no = no;
this.name = name;
}
// 省略getter、setter方法
}
将HeroNode转换成线索化二叉树:
class ThreadedBinaryTree {
private HeroNode pre = null;
# 完整代码在最后给出
public void threadedNodes(HeroNode node) {
if (node == null) {
return;
}
// 左子树线索化递归
threadedNodes(node.getLeft());
// 处理当前结点的前驱结点
if (node.getLeft() == null) {
node.setLeft(pre); // 第一次是8,指向自身
node.setLeftType(1); // 1表示指向前驱结点,0表示指向左子树
}
// 处理当前结点的的后继结点
if (pre != null && pre.getRight() == null) {
pre.setRight(node);
pre.setRightType(1);// 1表示指向前驱结点,0表示指向左子树
}
pre = node; // !!!一定要让下一个结点的前驱结点是当前结点
// 右子树线索化递归
threadedNodes(node.getRight()); // 8处理完后,根据栈内存原理就开始处理3
}
}
实现中序线索二叉树遍历思路:
- 定义一个变量指向根节点,存储当前遍历的结点,
- 循环遍历,根据leftType的值来判断,当前结点是指向子树还是结点,1指向结点,0指向子树,
- 按照上面规则一直遍历即可。
class ThreadedBinaryTree {
private HeroNode root;
public ThreadedBinaryTree(HeroNode root){
this.root = root;
}
// 遍历线索化二叉树
public void threadedList(){
if (root == null){
return;
}
HeroNode node = root;
while (node != null){
while (node.getLeftType() == 0){ //指向左子树
node = node.getLeft(); // 直到找到左结点
}
// 找到当前结点后打印前序结点
System.out.print(node.getNo()+" ");
// 判断当前结点的右指针是否指向后继结点
while (node.getRightType() == 1){
node = node.getRight();
System.out.print(node.getNo()+" "); // 打印后继结点
}
node = node.getRight(); //移动到下一个结点,后序结点就是下一个结点
}
}
}
完整代码:
package cn.cwj.threadBinaryTree;
public class ThreadedBinaryTreeDemo {
public static void main(String[] args) {
// 手动创建图示结点
HeroNode root = new HeroNode(1, "宋江"); // 根结点
HeroNode heroNode2 = new HeroNode(3, "吴用"); // 根节点的左子结点
HeroNode heroNode3 = new HeroNode(6, "武松"); // 根节点的右子结点
HeroNode heroNode4 = new HeroNode(8, "林冲"); // 根节点的右子结点的右子结点
HeroNode heroNode5 = new HeroNode(10, "李逵"); // 根节点的右子结点的右子结点
HeroNode heroNode6 = new HeroNode(14, "晁盖"); // 根节点的右子结点的右子结点
// 手动创建图示二叉树
root.setLeft(heroNode2);
root.setRight(heroNode3);
heroNode2.setLeft(heroNode4);
heroNode2.setRight(heroNode5);
heroNode3.setLeft(heroNode6);
// 测试
ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree(root);
threadedBinaryTree.threadedNodes();
// 测试10号结点的前驱和后序
HeroNode left = heroNode5.getLeft();
HeroNode right = heroNode5.getRight();
System.out.println(heroNode5.getNo() + "的前驱结点:" + left.getNo());
System.out.println(heroNode5.getNo() + "的后继结点:" + right.getNo());
//线索二叉树的遍历
System.out.println("线索二叉树的遍历:");
threadedBinaryTree.threadedList();
}
}
class ThreadedBinaryTree {
private HeroNode root;
private HeroNode pre = null;
public ThreadedBinaryTree(HeroNode root) {
this.root = root;
}
// 重载,减少传入参数
public void threadedNodes() {
this.threadedNodes(root);
}
public void threadedNodes(HeroNode node) {
if (node == null) {
return;
}
// 左子树线索化递归
threadedNodes(node.getLeft());
// 处理当前结点的前驱结点
if (node.getLeft() == null) {
node.setLeft(pre); // 第一次是8,指向自身
node.setLeftType(1); // 1表示指向前驱结点,0表示指向左子树
}
// 处理当前结点的的后继结点
if (pre != null && pre.getRight() == null) {
pre.setRight(node);
pre.setRightType(1);// 1表示指向前驱结点,0表示指向左子树
}
pre = node; // !!!一定要让下一个结点的前驱结点是当前结点
// 右子树线索化递归
threadedNodes(node.getRight()); // 8处理完后,根据栈内存原理就开始处理3
}
// 遍历线索化二叉树
public void threadedList() {
if (root == null) {
return;
}
HeroNode node = root;
while (node != null) {
while (node.getLeftType() == 0) { //指向左子树
node = node.getLeft(); // 直到找到左结点
}
// 找到当前结点后打印前序结点
System.out.print(node.getNo() + " ");
// 判断当前结点的右指针是否指向后继结点
while (node.getRightType() == 1) {
node = node.getRight();
System.out.print(node.getNo() + " "); // 打印后继结点
}
node = node.getRight(); //移动到下一个结点,后序结点就是下一个结点
}
}
}
class HeroNode {
private int no;
private String name;
private HeroNode left; // 左结点
private HeroNode right; // 右结点
private int leftType; // 0:指向左子树,1指向前驱结点
private int rightType; // 0:指向右子树,1指向后继结点
public HeroNode(int no, String name) {
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 + '\'' +
'}';
}
}