前言:🐻
前面讲完了,二叉树,顺序存储二叉树,哎呀,大家可能就有点松懈了,二叉树?就这?哼,今天开始给大家介绍不一样的树,这种树的难度绝对比之前难,好好看吧,今天我要介绍的是线索二叉树
我会从以下几点开始阐述
线索化二叉树的概述 🔜 线索化二叉树的创建思路 🔜 线索化二叉树的代码实现与分析 🔜 线索化二叉树的总结
如果喜欢博主的话,戳这 传送门
精彩频道:
SQL的DBMS区 🔛 Java的数据结构区 🔛 SE的基础知识点区
往期精彩:
目录
1线索化二叉树的概述🐼
1.1线索化二叉树产生原因😀
当我们写二叉树的时候,是否发现,完全二叉树,满二叉树,还有普通的二叉树都有或多或少的叶子节点,叶子节点又有很多的空引用(里面的left和right),我们发现,这些空引用利用的非常的不充分,而且一共有n+1个空引用,所以我们引入了线索化二叉树的概念
1.2线索化二叉树的定义😀
当我们把叶子节点的空引用,按照一定的规则连上,这就叫做二叉树的线索化
1.3线索化二叉树的常用术语😀
- 前驱节点
- 前驱节点由左子节点指向,即左子节点保存前驱节点的内存地址
- 后继节点
- 后继节点由右子节点指向,即右子节点保存后继节点的内存地址
- 注意:
- 左子节点指向的情况有两种
- 左子树
- 前驱节点
- 右子树指向的情况有两种
- 右子树
- 后继节点
- 左子节点指向的情况有两种
1.4线索化二叉树的分类😀
- 前序线索二叉树
- 中序线索二叉树
- 后序线索二叉树
2线索化二叉树的创建思路🐼
注意:思路采用的是对中序线索化二叉树的思路分析
- 我们需要建立一个节点类(里面有data,left,leftType,right,richtType等属性),并提供对应的构造方法
- 建立一个线索化二叉树类(里面有root,pre等属性),并提供对应的构造方法
- 建立一个线索化二叉树的方法,参数是带线索化的节点
- 先校验该节点
- 若不为空则向左子树递归线索化
- 线索化当前节点
- 若不为空则向右子树递归线索化
- 建立一个遍历中序线索二叉树的方法
- 找第一个前驱节点
- 根据后继节点进行输出
成品图
3线索化二叉树的代码实现与分析🐼
3.1创建一个节点类
public class Node {
public int no;
public Node left;
public Node right;
public boolean leftType;
public boolean rightType;
public Node(int no) {
this.no = no;
}
public String toString() {
return no + "";
}
}
🦁代码分析:
- 属性:no(节点的权),left和right(左子节点和右子节点的内存地址),leftType与rightType(左子节点和右子节点的类型)
- 注意:若type为true,说明指向的是前驱节点或后继节点,若type为false,说明指向的是子树
- 添加一个构造方法,初始化里面的权值
- 重写toString方法,方便输出
3.1创建一个线索化二叉树类
public class ThreadBinaryTree {
// 前驱节点
private Node pre;
public Node root;
}
🦁代码分析:
- 属性:root(根节点的内存地址),pre(即前驱节点的内存地址)
3.1创建一个中序线索化的方法
public ThreadBinaryTree(Node root) {
this.root = root;
}
public void infixThreadedNodes(Node node) {
if (node == null) {
return;
}
if (node.left != null && !node.leftType) {
infixThreadedNodes(node.left);
}
if (node.left == null) {
node.left = pre;
node.leftType = true;
}
if (pre != null && pre.right == null) {
pre.right = node;
pre.rightType = true;
}
pre = node;
if (node.right != null && !node.rightType) {
infixThreadedNodes(node.right);
}
}
🦁代码分析:
- 注意:线索化的本质其实和遍历相似,我们可以从遍历的角度去思考该问题
- 形式参数列表中的node,表示待线索化的节点,由于每一次线索化节点都是从根节点开始的,所以我们利用重载机制,传入根节点,方便调用
- 首先,先判断一下待线索化的节点是否为空,若为空,则直接退出(校验意识)
- 如果左子节点不为空,且左字节点的引用存放的是子树的内存地址,就向左子树进行递归线索化(注意:一定要判断是否是子树,否则会导致线索化失败)
- 线索化当前节点
- 判断该该节的左子节点是否为空,如果为空,则将前驱节点pre赋值给左子节点
- 并将左子节点引用的类型改为true,表示为前驱节点
- 先阐述最后一个if,跟3一样,如果右子节点不为空,且右子节点的引用存放的是子树的内存地址,就向右子树进行递归线索化
- if (pre != null && pre.right == null)的具体阐述
- 由于这个结构和中序遍历一摸一样,所以中序输出的是什么元素,该方法线索化的就是什么元素,
- 中序输出存在一个顺序问题,该线索化也有一定的顺序,而且这两个的顺序是一样的,这说明了,下一次node节点线索化的元素就是上一次线索化的元素的后继节点,为我们打下了理论基础!
- 解释:
- 由于线索化节点的前进,pre也应该要前进一步(pre表示前驱节点或者是待线索化的上一个节点),即最后的pre = node(向前走一步),此刻的pre代表上一个待线索化的节点
- 先判断pre是否为空,若不为空则判断右子节点是否为空,若为空,则将右子节点赋值为node当前待线索化的节点(这个节点就是上个节点的后继节点),再把类型给改变了
- 通过1和2的操作就把第一个节点完全的线索化了,后面以此类推
- 注意:第一个节点的前驱节点是null,最后一个节点的后继节点是null,这就是为什么pre初始化为null
3.2创建一个遍历中序线索二叉树的方法
public void infixPrint() {
var node = root;
while (node != null) {
while (!node.leftType) {
node = node.left;
}
System.out.println(node);
while (node.rightType) {
System.out.println(node.right);
node = node.right;
}
node = node.right;
}
}
🦁代码分析:
- 从根节点开始输出,所以node = root
- 当node不为空时,说明当前节点,仍然存在,应当继续输出,即作为循环输出的条件
- while(!node.leftType)的理解(有点抽象,从中序遍历出发)
- 由于中序遍历是左根右,所以会先输出最左边的节点,这是理解的前提条件
- 如果当前节点的左子节点不是前驱节点,就向左子树去找,直到找到左子节点是前驱节点
- 由于一直往左走,肯定第一个找到的是最左边的节点.即中序遍历第一个输出的节点,此刻其左子节点确实是前驱节点,条件不满足,跳出循环
- 我们发现,刚好第一次是左字节带你是前驱节点的节点刚好是中序输出的第一个,故符合算法规律
- 输出当前节点
- 如果右子节点是当前节点的后继节点,说明符合中序输出的规则,应当立刻输出后继节点,并向节点移动
- 如果右子节点不是后继节点,则让node = node.right.向右子树移动
- 向右移动,其实也体现了左根右的右的思想
- 该做法的原因是,如果右子节点不是后继节点,应当被当作新的根节点,重复一次上面的逻辑,从而达到输出所有的节点(根据中序)
完整代码🐼
package datastructure.chapter04.tree.binarytree.threadbinarytree;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 江骏杰
* Date: 2022-03-26
* Time: 9:18
*/
public class Node {
public int no;
public Node left;
public Node right;
public boolean leftType;
public boolean rightType;
public Node(int no) {
this.no = no;
}
public String toString() {
return no + "";
}
}
package datastructure.chapter04.tree.binarytree.threadbinarytree;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 江骏杰
* Date: 2022-03-26
* Time: 9:12
*/
public class ThreadBinaryTree {
// 前驱节点
private Node pre;
public Node root;
public ThreadBinaryTree(Node root) {
this.root = root;
}
public void infixThreadedNodes() {
infixThreadedNodes(root);
}
public void infixThreadedNodes(Node node) {
if (node == null) {
return;
}
if (node.left != null && !node.leftType) {
infixThreadedNodes(node.left);
}
if (node.left == null) {
node.left = pre;
node.leftType = true;
}
if (pre != null && pre.right == null) {
pre.right = node;
pre.rightType = true;
}
pre = node;
if (node.right != null && !node.rightType) {
infixThreadedNodes(node.right);
}
}
public void infixPrint() {
var node = root;
while (node != null) {
while (!node.leftType) {
node = node.left;
}
System.out.println(node);
while (node.rightType) {
System.out.println(node.right);
node = node.right;
}
node = node.right;
}
}
}
结论:🐻
哈哈,看到这里是不是觉得优点难受,是不是优点怀疑人生,其实理解好中序遍历与中序线索化及其遍历的关系即可,代码思维不是一蹴而就的,一遍看不懂就看两遍,不会就找博主,博主给你解答.我来总结一下必须要掌握的几点
1.中序线索化及其输出与中序遍历的关系
2.节点的构建
3.pre的灵活用法
🚇下一站:堆排序(是顺序存储二叉树的算法实现)