二叉树的基本操作
创建二叉树的前置说明
二叉树是由 递归方法 创建,但一上来就直接创建,难免会觉得抽象,所以我们先手动快速创建一棵简单的二叉树,先了解二叉树结构,之后再反过头来研究二叉树真正的创建方式
手动创建一棵二叉树
-
以 穷举的方式 手动创建一棵如图所示的二叉树,树的表现形式用 —— 孩子表示法
-
树的表现形式
static class BTNode {
// 孩子表示法
char val;
BTNode left; // 左子树
BTNode right; // 右子树
BTNode(char val) {
this.val = val;
}
}
- 手动创建一棵二叉树
public BTNode createTree() {
// 1.先创建结点,这与递归创建树逻辑顺序一致
BTNode A = new BTNode('A');
BTNode B = new BTNode('B');
BTNode C = new BTNode('C');
BTNode D = new BTNode('D');
BTNode E = new BTNode('E');
BTNode F = new BTNode('F');
BTNode G = new BTNode('G');
BTNode H = new BTNode('H');
// 2.将结点之间联系起来
A.left = B;
B.left = D;
B.right = E;
E.right = H;
A.right = C;
C.left = F;
C.right = G;
// 3.返回根结点
return A;
}
实现树的遍历
理解树是怎么遍历的,可以很好地帮助我们去理解二叉树结构的运作方式。
遍历操作是二叉树上最重要的操作之一,是二叉树上进行其它运算的基础
- 树的遍历分为四种
- 前序遍历 —— 访问根结点 —> 根的左子树 —> 根的右子树
- 中序遍历 —— 根的左子树 —> 访问根结点 —> 根的右子树
- 后序遍历 —— 根的左子树 —> 根的右子树 —> 访问根结点
- 层序遍历 —— 从上到下,从左到右,依次遍历
前序遍历
- 图解:前序遍历就是 先打印根结点 —> 再到左结点 —> 最后右结点
- 该图的前序遍历:ABDEHCFG
-
记住一句话 —— 每一个结点都是 根结点 。只不过有些根结点比较特殊,它拥有父结点,根也有根。我们都是在根结点上进行操作
-
算法
- 递归实现,每遇见一个结点就打印
- 先左后右,直到遇到 null 返回
void preOrder(BTNode root) {
if (root == null) {
return;
}
System.out.print(root.val + " ");
preOrder(root.left);
preOrder(root.right);
}
关于递归的一些想法
- 用个比喻来描绘递归:递归就像在一个公司里面,我是一个经理,老总下达任务给我,告诉了我一个 方向 ,我一个人肯定是干不完这么多东西的,但是我可以下发给我的下级,也告诉他们一个方向 ,我不关心他们所做的过程,我只关心员工拿给我的成果。这个方向指向的就是我们最终的目的。
- 下级肯定也有他的下级,就这样层层下达,总有一个最下级,等这个最下级的人完成任务,然后将任务成功传给他的上级,就这样层层上传,等成果传给我手上后,我发现方向没错,我把我的任务也做完,再传给老总,就这样完成了一个目的
- 这就是我对递归的一些理解。上面的前序遍历,我告诉了我的员工应该走的方向,即 根 左 再右 ,没得打印了再反过头来递给我成果
中序遍历
-
图解:中序遍历就是 先打印左子树 —> 再到根结点 —> 最后右子树
-
该图的中序遍历:DBEHAFCG
-
算法
- 左边打印完,遇到null,才打印根结点
- 先左后右,直到遇到 null 返回
void inOrder(BTNode root) {
if (root == null) {
return;
}
inOrder(root.left);
System.out.print(root.val + " ");
inOrder(root.right);
}
后序遍历
-
图解:后序遍历就是 先打印左子树 —> 再到右子树 —> 最后根结点
-
该图的后序遍历:DHEBFGCA
-
算法
- 检测到根结点无左右孩子时,打印根结点
void postOrder(BTNode root) {
if (root == null) {
return;
}
postOrder(root.left);
postOrder(root.right);
System.out.print(root.val + " ");
}
层序遍历(BFS广度优先搜索)
层序遍历:从上到下,从左到右,依次遍历
前中后序遍历其本质都是从上到下,层序的关键点是 从左到右
- 思路:
- 与 队列 相结合,并加上一个辅助指针
- 结点进队列
- 当队列不为空时,出队,用辅助指针
cur
记录这个出队结点,再将这个出队结点的左右结点依次入队
图解:
//层序遍历
public void levelOrder(BTNode root) {
// 1.如果树为空,不打印
if (root == null) {
return;
}
// 2.与队列相结合。先将根结点入队
Deque<BTNode> deque = new LinkedList<>();
deque.offer(root);
// 3.当队列不为空时,进行出队操作
while (!deque.isEmpty()) {
// 4.使用辅助指针 cur 记录出队结点,并打印结点数值
BTNode cur = deque.poll();
System.out.print(cur.val + " ");
// 5.如果该结点有左子树,将左子树入队
if (cur.left != null) {
deque.offer(cur.left);
}
// 6.如果该结点有右子树,将右子树入队
if (cur.right != null) {
deque.offer(cur.right);
}
}
}
二叉树的基本操作
获取树中结点的个数
方法一:遍历
思路
- 前序遍历
- 将计数器
nodeSize
定义到方法外部。如果放在方法内部,每次递归计数器都会重新赋值
// 获取树中节点的个数:遍历思路
public static int nodeSize;
public int size(BTNode root) {
if (root == null) {
return 0;
}
nodeSize++;
size(root.left);
size(root.right);
return nodeSize;
}
方法二:化成子问题
思路
- 整棵树的结点总数 = 左子树的结点个数 + 右子树的结点个数 + 1(根结点)
- 如图:A 的左子树是 B,对 B 来说 B 本身也是一个根结点,B 的左子树是 D,不断细分,直到左边或者右边都没有结点
基于以上逻辑,代码如下:
// 获取节点的个数:子问题的思路
public int size2(BTNode root) {
if (root == null) {
return 0;
}
return size2(root.left) + size2(root.right) + 1;
}
获取叶结点个数
方法一:遍历
思路
- 判断为叶结点,计数器才开始计数
// 获取叶子节点的个数:遍历思路
public static int leafSize = 0;
public int getLeafNodeCount1(BTNode root) {
// 空树没有结点,更别提叶子
if (root == null) {
return 0;
}
if(root.left == null && root.right == null) {
leafSize++;
}
getLeafNodeCount1(root.left);
getLeafNodeCount1(root.right);
return leafSize;
}
方法二:化为子问题
思路
- 整棵树的叶子结点 = 左子树叶子 + 右子树叶子
// 获取叶子节点的个数:子问题
public int getLeafNodeCount2(BTNode root) {
if (root == null) {
return 0;
}
if (root.left == null && root.right == null) {
return 1;
}
return getLeafNodeCount2(root.left)
+ getLeafNodeCount2(root.right);
}
获取第 K 层 结点的个数
思路
-
按照上面的 子问题思路 去解决这个问题,就非常好解决
-
整棵树的第 K 层结点个数 = 左子树的第 K-1 层结点个数 + 右子树的第 K-1 层结点个数
-
如图,假设求该图的第 1 层的结点个数,那肯定就是 1 个。
-
现在求第 2 层结点个数,其实就是求第 2 层的根结点个数,相对
A
结点来说,B、C
是第 2 层,但相对B、C
本身来说,他们就是第 1 层! -
所以,如何让计算机知道,这一层相对于它们本身来说是第 1 层,就是关键
-
这个关键点就是所给的
K
,K
也是要进行递归的,在A
的时候,K
为 2,到B、C
这一层的时候,K-1 = 1
,那就是第 1 层了,所以当K == 1
时,就说明这层是要去求的层数结点
// 获取第K层节点的个数
public int getKLevelNodeCount(BTNode root, int k) {
if (root == null) {
return 0;
}
// 当 K 等于1 时,说明这就是第一层
if (k == 1) {
return 1;
}
return getKLevelNodeCount(root.left,k-1)
+ getKLevelNodeCount(root.right,k-1);
}
求树的高度
思路
-
还是化成 子问题思路,化成一棵一棵子树来看,子树也是一棵树,将大树分成一棵棵小树,也是递归的核心思想
-
整棵树的高度 = max( 左子树高度,右子树高度) + 1
-
图解
// 获取二叉树的高度
// 时间复杂度:O(N)
public int getHeight(BTNode root) {
if (root == null) {
return 0;
}
int leftHeight = getHeight(root.left);
int rightHeight = getHeight(root.right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
检测值为 val 的元素是否存在
思路
- 遍历树,找到
val
所在结点,返回这个结点- 空树返回
null
- 找根,再往左子树找,随后再往右子树找,结点数据不是
val
的统统返回null
- 空树返回
// 检测值为value的元素是否存在
public BTNode find(BTNode root, char val) {
if (root == null) {
return null;
}
if (root.val == val) {
return root;
}
BTNode left = find(root.left,val);
if (left != null) {
return left;
}
BTNode right = find(root.right,val);
if (right != null) {
return right;
}
return null;
}