文章目录
一、二叉树概述
1.1 为什么要有树这种数据结构
在前面已经学习了数组和链表这两种数据结构,这两种数据结构都有着鲜明的特点。
数组存储方式的优缺点如下:
- 优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。
- 缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动, 效率较低。
链表存储方式的优缺点如下:
- 优点:在一定程度上对数组存储方式有优化。比如插入一个数值节点,只需要将插入节点链接到链表中即可,其删除效率也较高。
- 缺点:在进行检索时,效率仍然较低。比如检索某个值, 需要从头节点开始遍历。
从它们的特点我们可以总结出:数组方式查找快、增删慢,而链表方式查找慢、增删快。这两种数据存储方式都相当于是鱼与熊掌不可兼得。
而树这种数据结构,可以在查找速度快的同时又兼顾了增删的效率,可以说是结合了数组和链表的优点。
树存储方式的核心特点为:能提高数据存储、读取的效率。比如利用二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入、删除、修改的速度。
1.2 树的常用术语
树这种数据结构有较多的常用术语,这些术语我们需要熟练记住。
这些常用术语包括:
- 节点
- 根节点
- 父节点
- 子节点
- 叶子节点(没有子节点的节点)
- 节点的权(节点值)
- 路径(从 root 节点找到该节点的路线)
- 层
- 子树
- 树的高度(最大层数)
- 森林(多颗子树构成森林)
常用术语虽然比较多,但是对照着树的示意图,还是比较容易理解的。
1.3 什么是二叉树
树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。二叉树的子节点分为左节点和右节点。
如上图所示的三个树,它们每个节点最多只有两个子节点,因此它们都是二叉树。
在二叉树中,还有两种更为特殊的树,分别是:
-
满二叉树
如果二叉树的所有叶子节点都在最后一层,并且结点总数= 2^n -1 , n 为层数,则我们称其为满二叉树。
-
完全二叉树
如果二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边(相对于根节点)连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树。
我们不难看出:满二叉树是完全二叉树的特殊形态, 即如果一棵二叉树是满二叉树, 则它必定是完全二叉树。
1.4 二叉树的遍历顺序
二叉树的遍历共有三种情况:
-
前序遍历
先遍历根节点,再遍历左子树,最后遍历右子树。
-
中序遍历
先遍历左子树,再遍历根节点,最后遍历右子树。
-
后序遍历
先遍历左子树,再遍历右子树,最后遍历根节点。
根据这三种遍历顺序的定义,我们可以得知两个结论:
- 左子树总是在右子树的前面遍历;
- 前、中、后的遍历顺序指的是遍历根节点的顺序。
二、二叉树的基本操作
本节的关于二叉树的操作,都是对如下图所示的二叉树进行的:
如上所示的二叉树,每一个节点都是一个英雄节点 HeroNode
,每个节点记录着英雄的信息(编号、姓名)。
英雄节点模型如下:
/**
* 模拟二叉树的节点
*/
class HeroNode{
public int no;
public String name;
public HeroNode left; // 左子节点
public HeroNode right; // 右子节点
public HeroNode(int no, String name){
this.no = no;
this.name = name;
}
// 添加左子节点
public void addLeftNode(HeroNode node){
this.left = node;
}
// 添加右子节点
public void addRightNode(HeroNode node){
this.right = node;
}
// ......
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
除此之外,我们还需要创建一个二叉树模型 BinaryTree
,包含对二叉树的操作:
class BinaryTree{
private HeroNode root; // 根节点
public BinaryTree(HeroNode node){
this.root = node;
}
// ......
}
为了方便后续的代码测试,我们手动创建一个上图所示的二叉树:
HeroNode node_1 = new HeroNode(1, "宋江");
HeroNode node_2 = new HeroNode(2, "卢俊义");
HeroNode node_3 = new HeroNode(3, "吴用");
HeroNode node_4 = new HeroNode(4, "公孙胜");
HeroNode node_5 = new HeroNode(5, "关胜");
BinaryTree binaryTree = new BinaryTree(node_1);
node_1.addLeftNode(node_2);
node_1.addRightNode(node_3);
node_3.addLeftNode(node_4);
node_3.addRightNode(node_5);
2.1 二叉树的节点遍历
【案例需求】
使用前序、中序、后序分别遍历二叉树。
【思路分析】
上面说过,二叉树的遍历包括:前序遍历、中序遍历、后序遍历。
其中前序遍历的思路如下:
- 首先输出当前节点;
- 如果左子节点不为空,就对左子节点递归前序遍历;
- 如果右子节点不为空,就对右子节点递归前序遍历。
中序遍历的思路如下:
- 首先判断左子节点是否为空,如果不为空,就对左子节点递归中序遍历;
- 然后输出当前节点;
- 最后判断右子节点是否为空,如果不为空,就对右子节点递归中序遍历。
后序遍历的思路如下:
- 首先判断左子节点是否为空,如果不为空,就对左子节点递归后序遍历;
- 然后判断右子节点是否为空,如果不为空,就对右子节点递归后序遍历;
- 最后输出当前节点。
【代码实现】
英雄节点 HeroNode
负责对子树的具体操作,二叉树 BinaryTree
调用节点的具体操作来实现相关操作。
二叉树的遍历操作实现代码如下: