前言
本文主要讲解二叉树,顺序存储二叉树,线索化二叉树
数据结构与算法文章列表
数据结构与算法文章列表: 点击此处跳转查看
目录
(一)二叉树
(1)为什么使用树
1)分析数组优缺点
优点:按照索引查询元素速度快、能存储大量数据、按照索引遍历数组方便
缺点:根据内容查找元素速度慢、数组的大小一经确定不能改变、数组只能存储一种类型的数据、增加、删除元素效率慢、未封装任何方法,所有操作都需要用户自己定义。
数组示意图:
2) 分析链表的优缺点
优点:添加、删除比较方便
缺点:查找某个值时,需要从头节点开始遍历,效率比较低
操作示意图:
3) 树存储方式的分析
能提高数据存储,读取的效率。
案例: [7, 3, 10, 1, 5, 9, 12]
(2)树示意图
树的常用术语(结合示意图理解):
- 节点
- 根节点
- 父节点
- 子节点
- 叶子节点 (没有子节点的节点)
- 节点的权(节点值)
- 路径(从 root 节点找到该节点的路线)
- 层
- 子树
- 树的高度(最大层数)
- 森林 :多颗子树构成森林
(3)二叉树的概念
- 树中节点的度不大于2的有序树称为二叉树。
- 二叉树的子节点分为左节点和右节点
示意图:
- 如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则我们称为满二叉树。
- 深度为k,有n个结点的二叉树当且仅当其每一个结点都与深度为k的满二叉树中编号从1到n的结点一一对应时,我们称为完全二叉树。
(4)二叉树遍历的说明
- 前序遍历: 先输出父节点,再遍历左子树和右子树
- 中序遍历: 先遍历左子树,再输出父节点,再遍历右子树
- 后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点
- 总结: 看输出父节点的顺序,就确定是前序,中序还是后序
(5)二叉树遍历应用实例(前序,中序,后序)
应用实例的说明和思路
二叉树遍历代码(点击下面链接查看源代码)
二叉树遍历应用实例(前序,中序,后序)
(6)二叉树-查找指定节点(前序,中序,后序)
要求:
- 请编写前序查找,中序查找和后序查找的方法。
- 并分别使用三种查找方式,查找 heroNO = 5 的节点
- 并分析各种查找方式,分别比较了多少次
- 思路分析图解
代码(点击下面链接查看源代码):
二叉树-查找指定节点(前序,中序,后序)
(7)二叉树-删除节点
要求:
- 如果删除的节点是叶子节点,则删除该节点
- 如果删除的节点是非叶子节点,则删除该子树.
- 测试,删除掉 5 号叶子节点 和 3 号子树.
- 完成删除思路分析
代码(点击下面链接查看源代码):
二叉树-删除节点
(二)顺序存储二叉树
(1)顺序存储二叉树的概念
基本说明:
从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组,看右面的示意图。
要求:
- 右图的二叉树的结点,要求以数组的方式来存放 arr : [1, 2, 3, 4, 5, 6, 6]
- 要求在遍历数组 arr 时,仍然可以以前序遍历,中序遍历和后序遍历的方式完成结点的遍历
顺序存储二叉树的特点:
- 顺序二叉树通常只考虑完全二叉树
- 第 n 个元素的左子节点为 2 * n + 1
- 第 n 个元素的右子节点为 2 * n + 2
- 第 n 个元素的父节点为 (n-1) / 2
- n : 表示二叉树中的第几个元素(按 0 开始编号如图所示)
(2)顺序存储二叉树遍历
需求: 给你一个数组 {1,2,3,4,5,6,7},要求以二叉树前序遍历的方式进行遍历。 前序遍历的结果应当为1,2,4,5,3,6,7
public class ArrBinaryTreeDemo {
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
// 创建一个 ArrBinaryTree
ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);
arrBinaryTree.preOrder(); // 1,2,4,5,3,6,7
}
}
// 编写一个ArrayBinaryTree, 实现顺序存储二叉树遍历
class ArrBinaryTree {
private int[] arr;// 存储数据结点的数组
public ArrBinaryTree(int[] arr) {
this.arr = arr;
}
// 重载preOrder
public void preOrder() {
this.preOrder(0);
}
// 编写一个方法,完成顺序存储二叉树的前序遍历
/**
*
* @param index 数组的下标
*/
public void preOrder(int index) {
// 如果数组为空,或者 arr.length = 0
if (arr == null || arr.length == 0) {
System.out.println("数组为空,不能按照二叉树的前序遍历");
}
// 输出当前这个元素
System.out.println(arr[index]);
// 向左递归遍历
if ((index * 2 + 1) < arr.length) {
preOrder(2 * index + 1);
}
// 向右递归遍历
if ((index * 2 + 2) < arr.length) {
preOrder(2 * index + 2);
}
}
}
结果:
1
2
4
5
3
6
7
(3)顺序存储二叉树应用实例
八大排序算法中的堆排序,就会使用到顺序存储二叉树
(三)线索化二叉树
(1)问题
将数列 {1, 3, 6, 8, 10, 14 } 构建成一颗二叉树. n+1=7
问题分析:
- 当我们对上面的二叉树进行中序遍历时,数列为 {8, 3, 10, 1, 6, 14 }
- 但是 6, 8, 10, 14 这几个节点的左右指针,并没有完全的利用上.
- 如果我们希望充分的利用 各个节点的左右指针, 让各个节点可以指向自己的前后节点,怎么办?
- 解决方案-线索二叉树
(2)线索二叉树基本介绍
- n 个结点的二叉链表中含有 n+1 【公式 2n-(n-1)=n+1】 个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索")
- 这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种
- 一个结点的前一个结点,称为前驱结点
- 一个结点的后一个结点,称为后继结点
(3)线索二叉树应用案例
应用案例说明:将下面的二叉树,进行中序线索二叉树。中序遍历的数列为 {8, 3, 10, 1, 14, 6}
思路分析: 中序遍历的结果:{8, 3, 10, 1, 14, 6}
说明: 当线索化二叉树后,Node 节点的 属性 left 和 right ,有如下情况:
- left 指向的是左子树,也可能是指向的前驱节点. 比如 ① 节点 left 指向的左子树, 而 ⑩ 节点的 left 指向的就是前驱节点.
- right 指向的是右子树,也可能是指向后继节点,比如 ① 节点 right 指向的是右子树,而⑩ 节点的 right 指向的是后继节点
代码(点击下面链接查看源代码):
线索二叉树应用案例
(4)遍历线索化二叉树
- 说明:对前面的中序线索化的二叉树, 进行遍历
- 分析:因为线索化后,各个结点指向有变化,因此原来的遍历方式不能使用,这时需要使用新的方式遍历线索化二叉树,各个节点可以通过线型方式遍历,因此无需使用递归方式,这样也提高了遍历的效率。 遍历的次序应当和中序遍历保持一致。
- 代码(点击下面链接查看源代码):
遍历线索化二叉树