线索二叉树
n 个结点的二叉链表中含有 n+1 个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索"),这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded Binary Tree) 。
根据线索性质的不同,线索二叉树可分为
- 前序线索二叉树
- 中序线索二叉树
- 后序线索二叉树
以下面这个例子说明
在该二叉树中一共有 6 个结点,有 7 个空指针域(3 有一个,4, 5, 6各有两个),根据遍历次序的不同可以给空指针域存放前一个结点或者后一个结点,也就是前驱结点和后继结点。
1. 结点类
class Node {
int id;
Node left;
Node right;
Node parent; // 后序线索化用到
int leftType; // 0 代表left是左子树 1代表left是前驱结点
int rightType; // 0 代表right是左子树 1代表right是后继结点
public Node(int id) {
this.id = id;
}
@Override
public String toString() {
return "Node{" +
"ID=" + id +
'}';
}
}
2. 二叉树类代码
二叉树类中的方法具体实现请看后文,在前序线索二叉树,中序线索二叉树,后序线索二叉树中详述。
class ThreadedBinaryTree {
Node root;
Node pre = null;// 在递归进行线索化时,pre 总是保留前一个结点
public ThreadedBinaryTree(Node root) {
this.root = root;
}
// 二叉树进行前序线索化的重载方法
public void preThreadedBinaryTree() {}
// 二叉树进行前序线索化的方法
public void preThreadedBinaryTree(Node node) {}
// 遍历打印 前序线索化二叉树的方法
public void preThreadedPrint() {}
// 二叉树进行中序线索化的重载方法
public void infixThreadedBinaryTree() {}
// 二叉树进行中序线索化的方法
public void infixThreadedBinaryTree(Node node) {}
// 遍历打印 中序线索化二叉树的方法
public void infixThreadedPrint() {}
// 二叉树进行后序线索化的重载方法
public void postThreadedBinaryTree() {}
// 二叉树进行后序线索化的方法
public void postThreadedBinaryTree(Node node) {}
// 处理 parent 结点
public void addParent(Node node) {}
// 遍历打印 后序线索化二叉树的方法
public void postThreadedPrint() {}
}
3. 前序线索二叉树
该二叉树的前序遍历为 1->2->4->5->3->6。
- 4 结点的左空指针域指向它的前驱结点 2,右空指针域指向它的后继结点 5。
- 5 结点的左空指针域指向它的前驱结点 4,右空指针域指向它的后继结点 3。
- 3 结点的左指针不为空,右空指针域指向它的后继结点 6。
- 6 结点的左空指针域指向它的前驱结点 3,因为 6 结点是最后一个结点,所以右空指针域依旧指向空。
前序线索二叉树之后如下图所示
代码
// 二叉树进行前序线索化的重载方法
public void preThreadedBinaryTree() {
preThreadedBinaryTree(root);
}
// 二叉树进行前序线索化的方法
public void preThreadedBinaryTree(Node node) {
if (node == null) { // 空树不能线索化
return;
}
// 处理当前结点的前驱
if (node.left == null) {
node.left = pre;
node.leftType = 1;
}
// 处理前一个结点的后继结点
if (pre != null && pre.right == null) {
pre.right = node;
pre.rightType = 1;
}
// 更新 pre结点,pre 指向当前结点
pre = node;
// 如果 left 是左子树时,线索化左子树,如果不判断会死递归
if (node.leftType == 0) {
preThreadedBinaryTree(node.left);
}
// 如果 right 是右子树时,线索化右子树,如果不判断会死递归
if (node.rightType == 0) {
preThreadedBinaryTree(node.right);
}
}
// 遍历打印 前序线索化二叉树的方法
public void preThreadedPrint() {
Node node = root;
while (node != null) {
// 处理当前结点
System.out.println(node);
while (node.rightType == 1) {
node = node.right;
System.out.println(node);
}
if (node.leftType == 0) { // 先左子树
node = node.left;
} else { // 没有左子树的话,看右子树(node.rightType == 0)
node = node.right;
}
}
}
测试一下
public static void main(String[] args) {
// 结点
Node root = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
Node node4 = new Node(4);
Node node5 = new Node(5);
Node node6 = new Node(6);
// 手动创建二叉树
root.left = node2;
root.right = node3;
node2.left = node4;
node2.right = node5;
node3.left = node6;
// 线索化
ThreadedBinaryTree threadedBinaryTree1 = new ThreadedBinaryTree(root);
threadedBinaryTree1.preThreadedBinaryTree();
// 遍历 前序线索化二叉树
System.out.println("--------遍历 前序线索化二叉树--------");
threadedBinaryTree1.preThreadedPrint();
}
运行一下
--------遍历 前序线索化二叉树--------
Node{ID=1}
Node{ID=2}
Node{ID=4}
Node{ID=5}
Node{ID=3}
Node{ID=6}
4. 中序线索二叉树
该二叉树的中序遍历为 4->2->5->1->6->3。
- 4 结点为第一个结点,所以左空指针域指向空,右空指针域指向它的后继结点 2。
- 5 结点的左空指针域指向它的前驱结点 2,右空指针域指向它的后继结点 1。
- 6 结点的左空指针域指向它的前驱结点 1,右空指针域指向它的后继结点 3。
- 3 结点的左指针不为空,3 结点为最后一个结点,所以右空指针域指向空。
中序线索二叉树之后如下图所示
代码
// 二叉树进行中序线索化的重载方法
public void infixThreadedBinaryTree() {
infixThreadedBinaryTree(root);
}
// 二叉树进行中序线索化的方法
public void infixThreadedBinaryTree(Node node) {
if (node == null) { // 空树不能线索化
return;
}
// 线索化左子树
infixThreadedBinaryTree(node.left);
// 处理当前结点的前驱
if (node.left == null) {
node.left = pre;
node.leftType = 1;
}
// 处理前一个结点的后继结点
if (pre != null && pre.right == null) {
pre.right = node;
pre.rightType = 1;
}
// 更新 pre结点,pre 指向当前结点
pre = node;
// 线索化右子树
infixThreadedBinaryTree(node.right);
}
// 遍历打印 中序线索化二叉树的方法
public void infixThreadedPrint() {
Node node = root;
while (node != null) {
// 找到遍历的开头
while (node.leftType == 0) {
node = node.left;
}
// 处理当前结点
System.out.println(node);
// 打印后继结点
while (node.rightType == 1) {
node = node.right;
System.out.println(node);
}
// 替换这个遍历的结点
node = node.right;
}
}
测试一下
public static void main(String[] args) {
// 结点
Node root = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
Node node4 = new Node(4);
Node node5 = new Node(5);
Node node6 = new Node(6);
// 手动创建二叉树
root.left = node2;
root.right = node3;
node2.left = node4;
node2.right = node5;
node3.left = node6;
// 线索化
ThreadedBinaryTree threadedBinaryTree2 = new ThreadedBinaryTree(root);
threadedBinaryTree2.infixThreadedBinaryTree();
// 遍历 中序线索化二叉树
System.out.println("--------遍历 中序线索化二叉树--------");
threadedBinaryTree2.infixThreadedPrint();
}
运行一下
--------遍历 中序线索化二叉树--------
Node{ID=4}
Node{ID=2}
Node{ID=5}
Node{ID=1}
Node{ID=6}
Node{ID=3}
5. 后序线索二叉树
该二叉树的后序遍历为 4->5->2->6->3->1。
- 4 结点为第一个结点,所以左空指针域指向空,右空指针域指向它的后继结点 5。
- 5 结点的左空指针域指向它的前驱结点 4,右空指针域指向它的后继结点 2。
- 6 结点的左空指针域指向它的前驱结点 2,右空指针域指向它的后继结点 3。
- 3 结点的左指针不为空,右空指针域指向它的后继结点 1。
后序线索二叉树之后如下图所示
代码
在遍历后序线索二叉树中,需要添加 parent 指针,指向该结点的父节点,不然无法遍历完左子树直接去遍历右子树,需要通过父节点过去。
// 二叉树进行后序线索化的重载方法
public void postThreadedBinaryTree() {
postThreadedBinaryTree(root);
}
// 二叉树进行后序线索化的方法
public void postThreadedBinaryTree(Node node) {
if (node == null) { // 空树不能线索化
return;
}
// 如果 left 是左子树时,线索化左子树,如果不判断会死递归
if (node.leftType == 0) {
postThreadedBinaryTree(node.left);
}
// 线索化右子树
if (node.rightType == 0) {
postThreadedBinaryTree(node.right);
}
// 处理当前结点的前驱
if (node.left == null) {
node.left = pre;
node.leftType = 1;
}
// 处理前一个结点的后继结点
if (pre != null && pre.right == null) {
pre.right = node;
pre.rightType = 1;
}
// 更新 pre结点,pre 指向当前结点
pre = node;
}
// 处理 parent 结点
public void addParent(Node node) {
if (node != null) {
if (node.left != null) {
node.left.parent = node;
}
if (node.right != null) {
node.right.parent = node;
}
addParent(node.left);
addParent(node.right);
} else {
return;
}
}
// 遍历打印 后序线索化二叉树的方法
public void postThreadedPrint() {
Node node = root;
// 找到遍历的开头
while (node.leftType == 0 || node.rightType == 0) {
if (node.leftType == 0) { // 先左子树
node = node.left;
} else { // 没有左子树的话,看右子树(node.rightType == 0)
node = node.right;
}
}
Node preNode = null;
while (node != null) {
// 处理当前结点
if (node.rightType == 1) {
// 打印后继结点
System.out.println(node);
preNode = node;
node = node.right;
} else {
if (node.right == preNode) { // 如果上一个处理的结点是当前结点的右结点
System.out.println(node);
if (node == root) { // 遍历结束
return;
}
preNode = node;
node = node.parent;
} else { // 如果上一个处理的结点是当前结点的左结点
node = node.right;
// 继续找到遍历右子树的开头
while (node.leftType == 0 || node.rightType == 0) {
if (node.leftType == 0) { // 先左子树
node = node.left;
} else { // 没有左子树的话,看右子树(node.rightType == 0)
node = node.right;
}
}
}
}
}
}
测试一下
public static void main(String[] args) {
// 结点
Node root = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
Node node4 = new Node(4);
Node node5 = new Node(5);
Node node6 = new Node(6);
// 手动创建二叉树
root.left = node2;
root.right = node3;
node2.left = node4;
node2.right = node5;
node3.left = node6;
ThreadedBinaryTree threadedBinaryTree3 = new ThreadedBinaryTree(root);
// 先添加 parent 指针
threadedBinaryTree3.addParent(root);
// 线索化
threadedBinaryTree3.postThreadedBinaryTree();
// 遍历 前序线索化二叉树
System.out.println("--------遍历 后序线索化二叉树--------");
threadedBinaryTree3.postThreadedPrint();
}
运行结果
--------遍历 后序线索化二叉树--------
Node{ID=4}
Node{ID=5}
Node{ID=2}
Node{ID=6}
Node{ID=3}
Node{ID=1}