二叉树种类
满二叉树:每个非叶子节点都有且只有两个子节点。
和完全二叉树:除了最底层外,其他各层都是满的;最底层的节点都集中在左侧。
二叉搜索树:对于任意节点 u
,左子树上所有节
点的值都小于 u.val
,右子树上所有节点的值都大于 u.val
。
平衡二叉树:任意节点的左右子树高度差 ≤ 1。
二叉树的存储方式
「二叉树可以链式存储,也可以顺序存储。」
那么链式存储方式就用指针, 顺序存储的方式就是用数组。
遍历算法
-
深度优先遍历(DFS)
-
前序(Pre‑Order):根 → 左 → 右
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/ class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> res = new LinkedList<>();preorder(root, res);return res;}void preorder(TreeNode root,List<Integer> list){if(root == null){return;}list.add(root.val);preorder(root.left,list); preorder(root.right,list);} }
-
中序(In‑Order):左 → 根 → 右
class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> res = new LinkedList<>();inorder(root, res);return res;}void inorder(TreeNode root,List<Integer> list){if(root == null){return;}inorder(root.left,list);list.add(root.val);inorder(root.right,list);} }
-
后序(Post‑Order):左 → 右 → 根
class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> res = new LinkedList<>();inorder(root, res);return res;}void inorder(TreeNode root,List<Integer> list){if(root == null){return;} inorder(root.left,list); inorder(root.right,list);list.add(root.val);} }
-
-
广度优先遍历(BFS)/层序(Level‑Order)
-
按层自上而下、同层从左到右依次访问,通常用队列实现。
-
二叉树的定义
public class TreeNode {int val;TreeNode left;TreeNode right;TreeNode() {}TreeNode(int val) { this.val = val; }TreeNode(int val, TreeNode left, TreeNode right) {this.val = val;this.left = left;this.right = right;}
}
递归写法
-
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
-
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
-
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
以前序遍历为例:
1.首先,确认参数,因为我们访问的是节点,所以参数要包含节点对象,其次要返回访问的数值,所以也要包含一个list对象保存访问的值
2.每一层递归的终止条件就是遇见空节点,所以判断当前节点是否为空,为空就返回
3. 前序遍历是中左右的顺序,所以在单层递归的逻辑,是要先取中节点的数值,代码如下:
迭代解决遍历
前序:因为迭代使用栈,而出栈顺序是先进后出,所以我们在遍历的时候要改变遍历的顺序,先遍历右节点,在遍历左节点,这时候就是左节点先出,符合根左右的习惯
class Solution {public List<Integer> preorderTraversal(TreeNode root) {List<Integer> list = new ArrayList<>();if(root == null){return list;}Stack<TreeNode> s = new Stack<>();s.push(root);while(!s.isEmpty()){TreeNode node = s.pop();list.add(node.val);if(node.right != null){s.push(node.right);}if(node.left != null){s.push(node.left);}}return list;
}}
中序:在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
// 中序遍历顺序: 左-中-右 入栈顺序: 左-右
class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> result = new ArrayList<>();if (root == null){return result;}Stack<TreeNode> stack = new Stack<>();TreeNode cur = root;while (cur != null || !stack.isEmpty()){if (cur != null){stack.push(cur);cur = cur.left;}else{cur = stack.pop();result.add(cur.val);cur = cur.right;}}return result;}
}
后序:// 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
class Solution {public List<Integer> postorderTraversal(TreeNode root) {List<Integer> result = new ArrayList<>();if (root == null){return result;}Stack<TreeNode> stack = new Stack<>();stack.push(root);while (!stack.isEmpty()){TreeNode node = stack.pop();result.add(node.val);if (node.left != null){stack.push(node.left);}if (node.right != null){stack.push(node.right);}}Collections.reverse(result);return result;}
}
!!层次遍历
思路:需要借助辅助队列来完成统计,即一层一层的入队。
1.首先声明一个外部的辅助队列,用来统计所有的入队出队的值,
2.声明处理一层一层队列的方法,在方法内部创建一个内部队列用来处理每一层中出对入对的值
3.每一层结束的标记是什么,就是内部队列长度为0时,就结束.
public class Solution {public List<List<Integer>> levelOrder(TreeNode root) {List<List<Integer>> resList = new LinkedList<>();if (root == null) {return resList;}Queue<TreeNode> q = new LinkedList<>();q.add(root);while (!q.isEmpty()) {List<Integer> l = new ArrayList<>();int len = q.size();while (len > 0) {TreeNode node = q.poll();l.add(node.val);if (node.left != null) q.add(node.left);if (node.right != null) q.add(node.right);len--;}resList.add(l);}return resList;
}}
二叉树自底而上的层次遍历
给你二叉树的根节点 root
,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
利用LinkedList的addFirst()方法即可,每一次遍历完都将内部队列得到的值放在前面,即可倒序
!!注意如果将res实际实现为List类型,就无法使用addFirst()方法
LinkedList<List<Integer>> res = new LinkedList<>();
public List<List<Integer>> levelOrder(TreeNode root) {LinkedList<List<Integer>> res = new LinkedList<>();if (root == null) {return null;}Queue<TreeNode> q = new LinkedList<>();q.add(root);while (!q.isEmpty()) {List<Integer> l = new LinkedList<>();int len = q.size();while (len-- > 0) {TreeNode node = q.poll();l.add(node.val);if (node.left != null) q.add(node.left);if (node.right != null) q.add(node.right);}res.addFirst(l);}return res;}
二叉树的右视图
给定一个二叉树的 根节点 root
,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
思路很简单,即每层层序遍历时都将最右边的节点值加入到LIst中,如何判断为最右边的节点?
即每一层处理时,队列长度仅为1时就是最右边的节点
class Solution {public List<Integer> rightSideView(TreeNode root) {List<Integer> res = new ArrayList<>();if (root == null) {return res;}Queue<TreeNode> q = new LinkedList<>();q.add(root);while (!q.isEmpty()) {int len = q.size();while (len > 0) {TreeNode node = q.poll();if(len == 1){res.add(node.val);}if (node.left != null) q.add(node.left);if (node.right != null) q.add(node.right);len--;}}return res;}
}
完全二叉树的节点个数
很简单, 利用层次遍历计算每一次处理队列中结点的数量即可
class Solution {public int countNodes(TreeNode root) {Queue<TreeNode> q = new LinkedList<>();if(root == null){return 0;}q.add(root);int count=0;while(!q.isEmpty()){int size = q.size();for(int i=0;i<size;i++){TreeNode node = q.poll();count++;if(node.left !=null)q.add(node.left);if(node.right !=null)q.add(node.right);}}return count;}
}
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。
二叉树的最大深度
使用层次遍历到最底一层的节点,既可以判断深度大小
class Solution {/*** 迭代法,使用层序遍历*/public int maxDepth(Node root) {if (root == null) return 0;int depth = 0;Queue<Node> que = new LinkedList<>();que.offer(root);while (!que.isEmpty()){depth ++;int len = que.size();while (len > 0){Node node = que.poll();for (int i = 0; i < node.children.size(); i++)if (node.children.get(i) != null) que.offer(node.children.get(i));len--;}}return depth;}
}
递归用法
class Solution {public int maxDepth(TreeNode root) {//终止条件:当树为空时结束递归,并返回当前深度0if(root == null){return 0;}//root的左、右子树的最大深度int leftDepth = maxDepth(root.left);int rightDepth = maxDepth(root.right);//返回的是左右子树的最大深度+1return Math.max(leftDepth, rightDepth) + 1;}
}
通过二叉树的最大深度递归用法,我们可以总结一下里面递归的过程,比如有一个{1,2,3,4,5,6,7,8}的完全二叉树
1.首先,节点1不为空,进入栈内,然后先调用左子树,这时,节点2入栈,4入栈,8入栈,因为每次都先判断的左子树。
2.这时候栈内又递归调用的maxdepth(8),maxdepth(4),maxdepth(2),maxdepth(1),然后节点8没有左右子树,所以leftdepth和rightdepth都是0,然后返回0+1,这是maxdepth(8)出栈了,回到maxdepth(4),这时候传递的leftdepth=1,因为8时4的左节点,节点4没有右节点,所以rightdepth=0,这是返回1+1,4出栈,返回maxdepth(2),leftdepth=2,rightdepth=1,所以返回2+1,2出栈,返回maxdepth(1),leftdepth=3,进入右子树。。。。
3.最后又返回maxdepth(1),leftdepth=3,rightdepth=2,所以最后的值返回的时3+1
递归调用栈过程
-
节点1入栈:
-
调用左子树
maxDepth(2)
。
-
-
节点2入栈:
-
调用左子树
maxDepth(4)
。
-
-
节点4入栈:
-
调用左子树
maxDepth(8)
。
-
-
节点8入栈:
-
左子树为空,返回
0
。 -
右子树为空,返回
0
。 -
当前节点8的深度为
max(0, 0) + 1 = 1
,出栈。
-
-
回到节点4:
-
leftDepth = 1
(来自节点8)。 -
调用右子树
maxDepth(null)
,返回0
。 -
当前节点4的深度为
max(1, 0) + 1 = 2
,出栈。
-
-
回到节点2:
-
leftDepth = 2
(来自节点4)。 -
调用右子树
maxDepth(5)
。
-
-
节点5入栈:
-
左子树为空,返回
0
。 -
右子树为空,返回
0
。 -
当前节点5的深度为
max(0, 0) + 1 = 1
,出栈。
-
-
回到节点2:
-
rightDepth = 1
(来自节点5)。 -
当前节点2的深度为
max(2, 1) + 1 = 3
,出栈。
-
-
回到节点1:
-
leftDepth = 3
(来自节点2)。 -
调用右子树
maxDepth(3)
。
-
-
节点3入栈:
-
调用左子树
maxDepth(6)
。
-
-
节点6入栈:
-
左子树为空,返回
0
。 -
右子树为空,返回
0
。 -
当前节点6的深度为
max(0, 0) + 1 = 1
,出栈。
-
-
回到节点3:
-
leftDepth = 1
(来自节点6)。 -
调用右子树
maxDepth(7)
。
-
-
节点7入栈:
-
左子树为空,返回
0
。 -
右子树为空,返回
0
。 -
当前节点7的深度为
max(0, 0) + 1 = 1
,出栈。
-
-
回到节点3:
-
rightDepth = 1
(来自节点7)。 -
当前节点3的深度为
max(1, 1) + 1 = 2
,出栈。
-
-
回到节点1:
-
rightDepth = 2
(来自节点3)。 -
当前节点1的深度为
max(3, 2) + 1 = 4
,出栈。
-
判断平衡二叉树
平衡二叉树每个节点的左右两个子树的高度差的绝对值不超过1。而本体并不是考察的深度,而是高度,每一颗二叉子树都要符合高度差的绝对值不超过1