一 前言
没有良好的数据结构基础根本支持不起深度研究,故知识追寻者发了大力气写一篇通俗易懂的树概念,希望读者们可以收获颇多;本篇文章将带领读者理解什么是树,树具有哪些特性,常见树的类别,简单实现等,尊重原创,转载请联系知识追寻者,知识追寻者系列文章仅供个人学习,不得用于商业用途;
二 树的概念与特性
2.1 树的概念
树是类似于链表的线性结构,但又是一种典型的非线性的结构;树具有很强的层级性,相比于线性结构,其时间复杂度更低,效率更高;读者可以联系,生活中看见的树;
2.2 树的术语
先看一张树的图片如下,去除图中的箭头和相关术语,树就是一种非线性的层级结构;
树的相关术语如下:
- 根节点: 没有父节点的节点,上图 A节点;
- 兄弟节点:具有相同的父节点的孩子节点;比如 F,G互为兄弟节点;
- 叶子节点:
没有孩子节点的节点
;比如D,F,G,I,J; - 祖先节点与孙节点:如果存在根节点A到节点 J 的路径,并且存在节点E出现在该路径上,则称E是节点 J 的祖先节点,J是 E 的孙节点;A B E H 都可以算是 J 的祖先节点;
- 节点大小:节点的大小是指节点
拥护的孙节点个数
,包括自身; 比如E 节点大小为4; - 节点的深度:指根节点到节点的
路径长度
; 比如 (A-B-D ), 两个 链,即D节点的深度为2; - 节点的高度:指节点到最深节点的
路径长度
;比如 (E - H -J), 两个链, 即E节点的高度为2; - 树的层级:具有相同深度的集合称为树的层级;比如 B和C ; 又比如 D,E,F,G
- 树的高度和深度: 树的深度指所有节点中深度的最大值,树的高度指所有节点中高度的最大值;
树的高度等于树的深度
2.3 树的类型
二叉树: 如果一棵树中每个节点有0个或者1个或者2个节点,则称该树为二叉树;故空树也是二叉树;
严格二叉树: 树的每个节点都有左孩子右孩子或者没有孩子;
斜树:斜树的每个节点只有一个孩子;若斜树的每个节点都只有左孩子则称为左斜树,若斜树的每个节点只有右孩子则称为右斜树;
满二叉树:所有的父节点都存在左孩子和右孩子,并且所有的叶子结点都在同一层上,则称该类树为满二叉树
完全二叉树:对于一棵具有n个节点的二叉树按照层级编号,同时,左右孩子按照先左孩子后右孩子编号,如果编号为i的节点与同样深度的满二叉树中编号为i的节点在二叉树中的位置完全相同,则这棵二叉树称为完全二叉树;故满二叉树一定是完全二叉树,反之不成立
2.4满二叉树的性质
满叉树的节点个数: 假设满二叉树层级为k,根据 数学归纳法和等比数列求和公式可得 2^0 + 2^1 +...+ 2^k = 2^(k+1) - 1; 推导过程如下图;
满二叉树的叶子节点个数:根据满二叉树的结构可知,第k层就是叶子节点所在的层,故叶子节点个数为 2^k个
三 二叉树的实现
3.1 二叉树的结构
根据二叉树的结构可知,每个节点都可以假设有左孩子和右孩子,则可以对应为 左指针和右指针,并且每个节点上都可以存储值;故经过面向对象的编程思想进行抽象后的类如下
/**
* @Author lsc
* <p>二叉树的结构 </p>
*/
public class TreeNode {
// 左孩子
private TreeNode leftNode;
// 右孩子
private TreeNode rightNode;
// 存储值
private Object value;
// 构造方法
TreeNode(Object value){
this.value = value;
}
// 省略 set get
}
现在需要实现以下的一颗满二叉树;
思路如下:
首先根节点储存1;然后分别储存 左孩子2,右孩子3;
其次左孩子节点作为父节点,然后分别储存 左孩子4,右孩子5;
最后右孩子节点作为父节点然后分别储存 左孩子6,右孩子7;
代码实现如下:
public static void main(String[] args) {
// 初始化树
TreeNode tree = initTree();
}
public static TreeNode initTree(){
// 创建7个节点
TreeNode treeNode1 = new TreeNode(1);
TreeNode treeNode2 = new TreeNode(2);
TreeNode treeNode3 = new TreeNode(3);
TreeNode treeNode4 = new TreeNode(4);
TreeNode treeNode5 = new TreeNode(5);
TreeNode treeNode6 = new TreeNode(6);
TreeNode treeNode7 = new TreeNode(7);
// 根据上面思路对节点进行组装
// 组装根节点
treeNode1.setLeftNode(treeNode2);
treeNode1.setRightNode(treeNode3);
// 组装左孩子
treeNode2.setLeftNode(treeNode4);
treeNode2.setRightNode(treeNode5);
// 组装右孩子
treeNode3.setLeftNode(treeNode6);
treeNode3.setRightNode(treeNode7);
return treeNode1;
}
3.2 二叉树的遍历与实现
二叉树的遍历分为前序遍历,中序遍历,后续遍历;假设 当前节点 为C (Current Node), 左节点为L ,右节点为R;
前序遍历:C----->L------->R
中序遍历:L----->C------->R
后续遍历:R----->C------>L
先序遍历的实现
思路 :
- 首先访问当前节点;
- 其次访问左节点;
- 最后访问后节点;
回到 前图 前序遍历CLR就是 1,2,4,5,3,6,7;
public static void main(String[] args) {
// 初始化树
TreeNode tree = initTree();
// 调用先序遍历
preOrderTree(tree);
}
/* *
* @Author lsc
* <p> 先序遍历</p>
* @Param [rootNode]
* @Return void
*/
public static void preOrderTree(TreeNode rootNode){
if (rootNode!=null){
// 值
System.out.println(rootNode.getValue());
// 左孩子
preOrderTree(rootNode.getLeftNode());
// 右孩子
preOrderTree(rootNode.getRightNode());
}
}
输出
1
2
4
5
3
6
7
先序遍历实现为线性实现,时间复杂度为O(n)
中序遍历的实现
思路 :
- 首先访问左节点
- 其次访问当前节点
- 最后访问右节点
回到 前图中序遍历的结果是 4,2,5,1, 6,3,7
public static void main(String[] args) {
// 初始化树
TreeNode tree = initTree();
// 调用中序遍历
middleOrderTree(tree);
}
/* *
* @Author lsc
* <p> 中序遍历</p>
* @Param [rootNode]
* @Return void
*/
public static void middleOrderTree(TreeNode rootNode){
if (rootNode!=null){
// 左孩子
middleOrderTree(rootNode.getLeftNode());
// 值
System.out.println(rootNode.getValue());
// 右孩子
middleOrderTree(rootNode.getRightNode());
}
}
输出
4
2
5
1
6
3
7
中序遍历实现为线性实现,时间复杂度为O(n)
后续遍历的实现
思路:
- 首先访问左节点
- 其次访问右节点
- 最后访问当前节点
回到 前图中序遍历的结果是 4,5,2,6, 7,3,1
public static void main(String[] args) {
// 初始化树
TreeNode tree = initTree();
// 调用后续遍历
postOrderTree(tree);
}
/* *
* @Author lsc
* <p>后续遍历 </p>
* @Param [rootNode]
* @Return void
*/
public static void postOrderTree(TreeNode rootNode){
if (rootNode!=null){
// 左孩子
postOrderTree(rootNode.getLeftNode());
// 右孩子
postOrderTree(rootNode.getRightNode());
// 值
System.out.println(rootNode.getValue());
}
}
输出
4
5
2
6
7
3
1
后序遍历实现为线性实现,时间复杂度为O(n)