一、基本介绍
-
n个结点的二叉链表中含有n+1 【公式 2n-(n-1)=n+1】 个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索")
-
这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种
-
一个结点的前一个结点,称为前驱结点
-
一个结点的后一个结点,称为后继结点
二、应用案例
思路分析:
节点8
的前驱节点为null,后继节点为节点3
节点10
的前驱节点为节点3
,后继节点为节点1
节点14
的前驱节点为节点1
,后继节点为节点6
中序遍历的结果:{8, 3, 10, 1, 14, 6}
说明:
当线索化二叉树后,Node节点的 属性 left 和 right ,有如下情况:
- left 指向的是左子树,也可能是指向的前驱节点. 比如 ① 节点 left 指向的左子树, 而 ⑩ 节点的 left 指向的就是前驱节点.
- right指向的是右子树,也可能是指向后继节点,比如 ① 节点right 指向的是右子树,而⑩ 节点的right 指向的是后继节点.
三、线索化代码实现
节点定义
/**
* 学生节点
*/
class StudentNode {
private int id;
private String name;
private StudentNode left;
private StudentNode right;
//父节点指针(后序线索化使用)
public StudentNode parent;
//若leftType=0表示当前指向的是左子树,如果是1则说明指向的是前驱节点
private int leftType;
//rightType=0表示当前指向的是右子树,如果是1则说明指向的是后继节点
private int rightType;
public StudentNode(int id, String name) {
this.id = id;
this.name = name;
}
// Get And Set
@Override
public String toString() {
return "StudentNode{id=" + id + ", name='" + name + "'}";
}
}
中序线索化
/**
* 线索化二叉树
*/
class ThreadedBinaryTree {
//根节点
private StudentNode root;
//辅助指针指向当前节点的前驱节点
private StudentNode pre = null;
public void setRoot(StudentNode root) {
this.root = root;
}
/**
* 遍历中序线索化二叉树入口
*/
public void infixThreadedNode() {
infixThreadedNode(root);
}
/**
* 对二叉树进行中序线索化
*/
private void infixThreadedNode(StudentNode node) {
//若传入的node为null则不能线索化
if (node == null) {
return;
}
// 一. 先线索化左子树
infixThreadedNode(node.getLeft());
// 二. 线索化当前节点
// 1. 处理当前节点的前驱节点
if (node.getLeft() == null) {
//让当前节点的左指针指向前驱节点
node.setLeft(pre);
//修改当前节点的左指针类型为前驱节点类型,这里用1代表
node.setLeftType(1);
}
// 2. 处理当前节点的后继节点 ==> 理解:因为当前节点的后继节点需要连接到当前节点的前驱节点,
// 所以可以理解为用 pre代替原本的node 从当前节点的前驱节点获取到当前节点再将当前节点的右指针指向下一个节点
if (pre != null && pre.getRight() == null) {
//让前驱节点的右指针指向当前节点
pre.setRight(node);
//修改前驱节点的右指针类型
pre.setRightType(1);
}
// 3. 处理好一个节点后,让当前节点的下一个节点是前驱节点
pre = node;
// 三.线索化右子树
infixThreadedNode(node.getRight());
}
}
前序线索化
/**
* 线索化二叉树
*/
class ThreadedBinaryTree {
//根节点
private StudentNode root;
//辅助指针指向当前节点的前驱节点
private StudentNode pre = null;
public void setRoot(StudentNode root) {
this.root = root;
}
/**
* 遍历前序线索化二叉树入口
*/
public void preThreadedNode() {
preThreadedNode(root);
}
/**
* 对二叉树进行前序线索化
*/
private void preThreadedNode(StudentNode node) {
//若传入的node为null则不能线索化
if (node == null) {
return;
}
// 一. 线索化当前节点
// 1. 处理当前节点的前驱节点
if (node.getLeft() == null) {
//让当前节点的左指针指向前驱节点
node.setLeft(pre);
//修改当前节点的左指针类型为前驱节点类型,这里用1代表
node.setLeftType(1);
}
// 2. 处理当前节点的后继节点 ==> 理解:因为当前节点的后继节点需要连接到当前节点的前驱节点,
// 所以可以理解为用 pre代替原本的node 从当前节点的前驱节点获取到当前节点再将当前节点的右指针指向下一个节点
if (pre != null && pre.getRight() == null) {
//让前驱节点的右指针指向当前节点
pre.setRight(node);
//修改前驱节点的右指针类型
pre.setRightType(1);
}
// 3. 处理好一个节点后,让当前节点的下一个节点是前驱节点
pre = node;
// 二. 先线索化左子树
if (node.getLeftType() == 0) {
preThreadedNode(node.getLeft());
}
// 三.线索化右子树
if (node.getRightType() == 0) {
preThreadedNode(node.getRight());
}
}
}
后序线索化
/**
* 线索化二叉树
*/
class ThreadedBinaryTree {
//根节点
private StudentNode root;
//辅助指针指向当前节点的前驱节点
private StudentNode pre = null;
public void setRoot(StudentNode root) {
this.root = root;
}
/**
* 遍历后序线索化二叉树入口
*/
public void postThreadedNode() {
postThreadedNode(root);
}
/**
* 对二叉树进行后序线索化
*/
private void postThreadedNode(StudentNode node) {
//若传入的node为null则不能线索化
if (node == null) {
return;
}
// 一. 先线索化左子树
postThreadedNode(node.getLeft());
// 二.线索化右子树
postThreadedNode(node.getRight());
// 三. 线索化当前节点
// 1. 处理当前节点的前驱节点
if (node.getLeft() == null) {
//让当前节点的左指针指向前驱节点
node.setLeft(pre);
//修改当前节点的左指针类型为前驱节点类型,这里用1代表
node.setLeftType(1);
}
// 2. 处理当前节点的后继节点 ==> 理解:因为当前节点的后继节点需要连接到当前节点的前驱节点,
// 所以可以理解为用 pre代替原本的node 从当前节点的前驱节点获取到当前节点再将当前节点的右指针指向下一个节点
if (pre != null && pre.getRight() == null) {
//让前驱节点的右指针指向当前节点
pre.setRight(node);
//修改前驱节点的右指针类型
pre.setRightType(1);
}
// 3. 处理好一个节点后,让当前节点的下一个节点是前驱节点
pre = node;
}
}
四、线索化后输出二叉树代码实现
中序线索化后输出二叉树
/**
* 线索化二叉树
*/
class ThreadedBinaryTree {
//根节点
private StudentNode root;
/**
* 中序遍历输出
*/
public void infixOrder() {
//定义一个变量,存储当前遍历的节点
StudentNode node = root;
while (node != null) {
//找到左子树最底节点非前驱节点
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();
}
}
}
前序线索化后输出二叉树
/**
* 线索化二叉树
*/
class ThreadedBinaryTree {
//根节点
private StudentNode root;
/**
* 前序遍历输出
*/
public void preOrder() {
//定义一个变量,存储当前遍历的节点
StudentNode node = root;
while (node != null) {
//找到左子树最底节点非前驱节点
while (node.getLeftType() == 0) {
System.out.println(node);
node = node.getLeft();
}
//如果当前节点的右指针指向后继节点,就一直输出
System.out.println(node);
node = node.getRight();
}
}
}
后序线索化后输出二叉树
/**
* 线索化二叉树
*/
class ThreadedBinaryTree {
//根节点
private StudentNode root;
/**
* 后序遍历输出
*/
public void postOrder() {
StudentNode node = root;
StudentNode preNode = null;
//1.找到后序遍历的开始节点 -> 最左子节点
while (node != null && node.getLeftType() == 0) {
node = node.getLeft();
}
while (node != null) {
//右节点是后继结点
if (node.getRightType() == 1) {
System.out.println(node);
//用临时指针preNode保存当前结点
preNode = node;
//指向下一个后继节点
node = node.getRight();
} else {
//若上一个结点是当前节点的右节点
if (preNode == node.getRight()) {
System.out.println(node);
//若当前结点是根节点则退出
if (node == root) {
return;
}
preNode = node;
//当前结点指向当前节点的父级节点
node = node.parent;
} else {
//上一个结点是当前节点的左节点,则找到当前子树的最左节点
node = node.getRight();
while (node != null && node.getLeftType() == 0) {
node = node.getLeft();
}
}
}
}
}
}
附 完整代码