数据结构08 线索二叉树
上一篇总结了二叉树,这一篇要总结的是线索二叉树,我想从以下几个方面进行总结。
1、什么是线索二叉树?
2、为什么要建立线索二叉树?
3、如何将二叉树线索化?
4、线索二叉树的常见操作及实现思路?
5、算法实现代码?
1、什么是线索二叉树
线索二叉树:
按照某种方式对二叉树进行遍历,可以把二叉树中所有节点排序为一个线性序列,在该序列中,除第一个节点外每个节点有且仅有一个直接前驱节点;除最后一个节点外每一个节点有且仅有一个直接后继节点;
在N个节点的二叉树中,每个节点有2个指针,所以一共有2N个指针,除了根节点以外,每一个节点都有一个指针从它的父节点指向它,所以一共使用了N-1个指针,所以剩下2N-(N-1)也就是N+1个空指针;
如果能利用这些空指针域来存放指向该节点的直接前驱或是直接后继的指针,则可由此信息直接找到在该遍历次序下的前驱节点或后继节点,从而比递归遍历提高了遍历速度,节省了建立系统递归栈所使用的存储空间;
这些被重新利用起来的空指针就被称为线索(Thread),加上了线索的二叉树就是线索二叉树;如图:
2、为什么要建立线索二叉树
有了二叉树不就足够了吗?那为什么还要弄个线索二叉树出来呢?
在原来的二叉链表中,查找节点的左,右孩子可以直接实现,可是如果要找该节点的前驱和后继节点呢?这就变得非常困难,所以为了实现这个常见的需求,我们要在每个节点中增加两个指针域来存放遍历时得到的前驱和后继节点,这样就可以通过该指针直接或间接访问其前驱和后继节点。
3、如何将二叉树线索化
按某种次序遍历二叉树,在遍历过程中用线索取代空指针即可。
下面是线索二叉树和线索二叉链表的示意图,它可以帮助我们更好地理解线索二叉树。
4、线索二叉树的常见操作及实现思路
4-1、二叉树线索化
实现思路:按某种次序遍历二叉树,在遍历过程中用线索取代空指针即可。
4-2、遍历
实现思路:以中序遍历为例,首先找到中序遍历的开始节点,然后利用线索依次查找后继节点即可。
5、实现代码
代码:
Node.java
public class Node {
private int data;
private Node left;
private boolean leftIsThread; // 左孩子是否为线索
private Node right;
private boolean rightIsThread; // 右孩子是否为线索
public Node(int data) {
this.data = data;
this.left = null;
this.leftIsThread = false;
this.right = null;
this.rightIsThread = false;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public Node getLeft() {
return left;
}
public void setLeft(Node left) {
this.left = left;
}
public boolean isLeftIsThread() {
return leftIsThread;
}
public void setLeftIsThread(boolean leftIsThread) {
this.leftIsThread = leftIsThread;
}
public Node getRight() {
return right;
}
public void setRight(Node right) {
this.right = right;
}
public boolean isRightIsThread() {
return rightIsThread;
}
public void setRightIsThread(boolean rightIsThread) {
this.rightIsThread = rightIsThread;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Node) {
Node temp = (Node) obj;
if (temp.getData() == this.data) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return super.hashCode() + this.data;
}
}
ThreadTree.java
public class ThreadTree {
private Node root; // 根节点
private int size; // 大小
private Node pre = null; // 线索化的时候保存前驱
public ThreadTree() {
this.root = null;
this.size = 0;
this.pre = null;
}
public ThreadTree(int[] data) {
this.pre = null;
this.size = data.length;
this.root = createTree(data, 1); // 创建二叉树
}
/**
* 创建二叉树
*
* @param data
* @param index
* @return
*/
public Node createTree(int[] data, int index) {
if (index > data.length) {
return null;
}
Node node = new Node(data[index - 1]);
Node left = createTree(data, 2 * index);
Node right = createTree(data, 2 * index + 1);
node.setLeft(left);
node.setRight(right);
return node;
}
/**
* 将以root为根节点的二叉树线索化
*/
public void inThread(Node root) {
if (root != null) {
inThread(root.getLeft()); // 线索化左孩子
if (null == root.getLeft()) { // 左孩子为空
root.setLeftIsThread(true); // 将左孩子设置为线索
root.setLeft(pre);
}
if (pre != null && null == pre.getRight()) { // 右孩子为空
pre.setRightIsThread(true);
pre.setRight(root);
}
pre = root;
inThread(root.getRight()); // 线索化右孩子
}
}
/**
* 中序遍历线索二叉树
*/
public void inThreadList(Node root) {
if (root != null) {
while (root != null && !root.isLeftIsThread()) {
// 如果左孩子不是线索
root = root.getLeft();
}
do {
System.out.print(root.getData() + " ");
if (root.isRightIsThread()) {
// 如果右孩子是线索
root = root.getRight();
} else {
// 有右孩子
root = root.getRight();
while (root != null && !root.isLeftIsThread()) {
root = root.getLeft();
}
}
} while (root != null);
}
}
/**
* 中序递归遍历
*/
public void inList(Node root) {
if (root != null) {
inList(root.getLeft());
System.out.print(root.getData() + " ");
inList(root.getRight());
}
}
public Node getRoot() {
return root;
}
public void setRoot(Node root) {
this.root = root;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
}
ThreadTreeTest.java
public class ThreadTreeTest {
public static void main(String[] args) {
int[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
ThreadTree threadTree = new ThreadTree(data); // 创建普通二叉树
System.out.println("中序递归遍历二叉树");
threadTree.inList(threadTree.getRoot()); // 中序递归遍历二叉树
System.out.println();
threadTree.inThread(threadTree.getRoot()); // 采用中序遍历将二叉树线索化
System.out.println("中序遍历线索化二叉树");
threadTree.inThreadList(threadTree.getRoot()); // 中序遍历线索化二叉树
}
}
运行结果:
6、总结
由于它充分利用了空指针域的空间(等于节省了空间),又保证了创建时的一次遍历就可以终生受用前驱、后继的信息(这意味着节省了时间),所以在实际问题中,如果所使用的二叉树需要经常遍历或查找节点时需要某种遍历中的前驱和后继,那么采用线索二叉链表的存储结构就是不错的选择。
- 剑指OFFER之从上往下打印二叉树(九度OJ1523)
- 给你的博客加上“Fork me on Github”彩带
- Android Studio添加PNG图片报错原因
- 剑指OFFER之包含min函数的栈(九度OJ1522)
- 使用VS2010开发Qt程序的一点经验
- 用Qt写软件系列五:一个安全防护软件的制作(3)
- 剑指OFFER之顺时针打印矩阵(九度OJ1391)
- 用Qt写软件系列五:一个安全防护软件的制作(2)
- 2018年值得关注的200场机器学习会议
- Linux开机启动(bootstrap)
- 剑指OFFER之树的子结构(九度OJ1520)
- 万物智联慧结成网:信息技术驱动物流产业转型升级
- 用Qt写软件系列五:一个安全防护软件的制作(1)
- Linux文件管理
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 「Mysql索引原理(十)」冗余和重复索引
- 「Mysql索引原理(十一)」索引和锁
- 「Mysql索引原理(十二)」索引案例1-支持多种过滤条件
- 「Mysql索引原理(十三)」索引案例2-避免多个范围条件
- 「Mysql索引原理(十四)」索引案例3-优化排序
- 字符仿真
- 「Mysql索引原理(十五)」维护索引和表-修复损坏的表
- 「Mysql索引原理(十六)」维护索引和表-更新索引统计信息
- 「Mysql索引原理(十七)」维护索引和表-减少索引和数据的碎片
- 「通信框架Netty4 源码解读(一)」起步,关于IO的简单总结,模拟一个redis客户端
- Unet实现文档图像去噪、去水印
- 「influxDB 原理与实践(一)」安装部署,实现基础的添加删除查询功能
- 「influxDB 原理与实践(二)」详解influxDB的写入与查询
- Nginx系列:https配置
- 笛卡尔积、等值连接、自然连接、外连接一文看懂