今日内容
- 二叉树理论基础
- 递归遍历
- 层序遍历
二叉树理论基础
二叉树的类型
做题时常见的二叉树主要有 满二叉树、完全二叉树 和 二叉搜索树。
满二叉树只有度为0和2的节点,且度为0的节点全部在同一层:
深度为 k 的满二叉树总共有 2 ^ k - 1 个节点。
完全二叉树除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。如图所示:
二叉搜索树则是有序且带数值的。二叉搜索树有如下特点:
- 若存在左子树,则根节点的值大于左子树上所有节点的值。
- 若存在右子树,则根节点的值小于右子树上所有节点的值。
- 左右子树也都是二叉搜索树。
二叉搜索树如下图所示:
在二叉搜索树中还有个平衡二叉搜索树,也被称为AVL(Adelson-Velsky and Landis)树。平衡二叉搜索树要么是空树,要么左右子树的高度绝对值不能大于 1,并且左右子树也是平衡二叉搜索树。如图所示:
二叉树的存储方式
二叉树有两种存储方式:链式存储 和 顺序存储。前者用指针,后者用数组。
链式存储的示意图如下:
顺序存储则是由数组存储,如下图所示:
用顺序存储的话,若父节点的下标为 i ,那么它的左子树为 i * 2 + 1,右子树为 i * 2 + 2
不过一般来说,链式存储更好理解,顺序存储只需要了解了解就好。
二叉树的遍历方式
二叉树有深度遍历方式和广度遍历方式。
深度遍历:碰到子树就进去,碰到叶子节点后返回。有前序/中序/后序遍历问题(递归法、迭代法)
广度遍历:一层一层遍历。有层序遍历问题(迭代法)
层序遍历很好理解,主要是前中后序遍历容易搞混。前中后序遍历中的“前中后”指的是根节点的顺序,比如前序遍历则说明按照“根左右”顺序遍历,中序遍历则是按照“左根右”顺序。
深度遍历一般用递归方式解决,而递归可以使用栈解决。
广度遍历一般则使用队列解决,因为队列先进先出的特性有助于一层层地遍历。
二叉树的定义
和链表一样,力扣平台默认给出了定义,但是我们还是应该自己手搓一下:
class TreeNode{
int val;
TreeNode left;
TreeNode right;
public TreeNode(){}
public TreeNode(int val, TreeNode left, TreeNode right){
this.val = val;
this.left = left;
this.right = right;
}
递归遍历
递归,非常非常非常容易被绕进去,原因就在于写递归前没有按照有效的步骤进行,纯纯按照玄学,这样肯定不行。
一般写递归时,要注意如下三点:
- 确定递归函数的参数和返回的值
- 设置递归函数的停止条件,以防栈溢出
- 确定单层递归的逻辑
下面就以二叉树的前中后序遍历为例。
Leetcode. 144 二叉树的前序遍历
前序遍历的次序为“根左右”,并且要求返回一个列表,列表中的元素以前序遍历次序排布。
- 先分析递归函数的参数和返回值。首先我们要操作的是二叉树的节点,所以肯定需要传入节点。再者,题目要求一个列表,所以也需要传入一个列表对其进行操作。因为列表是引用类型,所以不需要递归函数返回东西了。
- 再思考递归函数的停止条件。在这个函数中,只有当节点为空时,递归函数才会停下。所以停止条件就是当节点为空时。
- 最后确定单层处理逻辑。在进行遍历时,我们要先读取根节点的值,再去读取左右子树的值。这就是单层的处理逻辑。
根据上述的分析,我们就可以写出如下代码:
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
preOrder(root, result);
return result;
}
public void preOrder(TreeNode root, List<Integer> list){ // 参数和返回值
if (root == null){return;} // 停止条件
// 单层逻辑
list.add(root.val);
preOrder(root.left, list);
preOrder(root.right, list);
}
}
以这种思路,中序和后序遍历无非就是在单层逻辑上进行微调就好。
Leetcode. 94 二叉树的中序遍历
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
inOrder(root, result);
return result;
}
public void inOrder(TreeNode root, List<Integer> list){
if (root == null){return;}
// 不同之处
inOrder(root.left, list);
list.add(root.val);
inOrder(root.right, list);
}
}
Leetcode. 145 二叉树的后序遍历
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
postOrder(root, result);
return result;
}
public void postOrder(TreeNode root, List<Integer> list){
if (root == null){return;}
postOrder(root.left, list);
postOrder(root.right, list);
list.add(root.val);
}
}
层序遍历
正如之前提到的,层序遍历可以使用队列实现。因为队列先入先出的特性,非常适合一层一层去遍历元素。其原理如下图所示:
根据上述图示,我们可以写出这样一套模板:
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null){return result;}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()){
int len = queue.size();
List<Integer> temp = new ArrayList<>();
// 每次 for 循环都表示遍历一层
for (int i = 0; i < len; i++){
TreeNode elem = queue.peek();
temp.add(elem.val);
queue.poll();
if (elem.left != null){queue.offer(elem.left);}
if (elem.right != null){queue.offer(elem.right);}
}
result.add(temp);
}
return result;
}
}
同样根据上面的思路,可以写出一个递归的模板:
class Solution {
public static void level(TreeNode n, List<List<Integer>> list, int depth){
if (n == null){return;}
if (list.size() == depth){
List<Integer> temp = new ArrayList<Integer>();
list.add(temp);
}
list.get(depth).add(n.val);
level(n.left, list, depth + 1);
level(n.right, list, depth + 1);
}
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null){return result;}
level(root, result, 0);
return result;
}
}
Leetcode. 102 二叉树的层序遍历
代码如下:
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null){return result;}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()){
//TreeNode n = queue.peek();
int len = queue.size();
List<Integer> temp = new ArrayList<>();
for (int i = 0; i < len; i++){
TreeNode elem = queue.peek();
temp.add(elem.val);
queue.poll();
if (elem.left != null){queue.offer(elem.left);}
if (elem.right != null){queue.offer(elem.right);}
}
result.add(temp);
}
return result;
}
}
Leetcode. 107 二叉树的层序遍历Ⅱ
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null){return result;}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()){
List<Integer> temp = new LinkedList<>();
int len = queue.size();
for (int i = 0; i < len; i++){
TreeNode n = queue.peek();
queue.poll();
temp.add(n.val);
if (n.left != null){queue.offer(n.left);}
if (n.right != null){queue.offer(n.right);}
}
result.add(temp);
}
// 加个反转
Collections.reverse(result);
return result;
}
}
Leetcode. 199 二叉树的右视图
class Solution {
public List<Integer> rightSideView(TreeNode root) {
/*
本题特殊之处在于取右视图
那么基本设想就是找每一层的最后一个元素
*/
List<Integer> result = new ArrayList<>();
if (root == null){return result;}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()){
int len = queue.size();
for (int i = 0; i < len; i++){
TreeNode n = queue.peek();
queue.poll();
if (i == len - 1){result.add(n.val);}
if (n.left != null){queue.offer(n.left);}
if (n.right != null){queue.offer(n.right);}
}
}
return result;
}
}
Leetcode. 637 二叉树的层平均值
class Solution {
public List<Double> averageOfLevels(TreeNode root) {
List<Double> result = new ArrayList<>();
if (root == null){return result;}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()){
long sum = 0;
double ave = 0.0;
int len = queue.size();
for (int i = 0; i < len; i++){
TreeNode n = queue.peek();
queue.poll();
sum += n.val;
if (n.left != null){queue.offer(n.left);}
if (n.right != null){queue.offer(n.right);}
}
ave = (double)sum / len;
result.add(ave);
}
return result;
}
}
Leetcode. 429 N叉树的层序遍历
class Solution {
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null){return result;}
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()){
List<Integer> temp = new ArrayList<>();
int len = queue.size();
Node n;
for (int i = 0; i < len; i++){
n = queue.peek();
queue.poll();
temp.add(n.val);
List<Node> nodeTemp = n.children;
for (int j = 0; j < nodeTemp.size(); j++){
queue.offer(nodeTemp.get(j));
}
}
result.add(temp);
}
return result;
}
}
Leetcode. 515 在每个树行中找最大值
class Solution {
public List<Integer> largestValues(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){return result;}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()){
int len = queue.size();
int max = 0;
for (int i = 0; i < len; i++){
TreeNode n = queue.peek();
if (i == 0){
max = n.val;
} else {
max = max > n.val ? max : n.val;
}
queue.poll();
if (n.left != null){queue.offer(n.left);}
if (n.right != null){queue.offer(n.right);}
}
result.add(max);
}
return result;
}
}
Leetcode. 116 填充每个节点的下一个右侧节点指针
class Solution {
public Node connect(Node root) {
if (root == null){return root;}
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()){
int len = queue.size();
for (int i = 0; i < len; i++){
Node n = queue.peek();
queue.poll();
if (i == len - 1){
n.next = null;
} else {
n.next = queue.peek();
}
if (n.left != null){queue.offer(n.left);}
if (n.right != null){queue.offer(n.right);}
}
}
return root;
}
}
Leetcode. 117 填充每个节点的下一个右侧节点指针Ⅱ
class Solution {
public Node connect(Node root) {
if (root == null){return root;}
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()){
int len = queue.size();
for (int i = 0; i < len; i++){
Node n = queue.peek();
queue.poll();
if (i == len - 1){
n.next = null;
} else {
n.next = queue.peek();
}
if (n.left != null){queue.offer(n.left);}
if (n.right != null){queue.offer(n.right);}
}
}
return root;
}
}
Leetcode. 104 二叉树的最大深度
class Solution {
public int maxDepth(TreeNode root) {
if (root == null){return 0;}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int depth = 0;
while (!queue.isEmpty()){
int len = queue.size();
depth += 1;
for (int i = 0; i < len; i++){
TreeNode n = queue.peek();
queue.poll();
if (n.left != null){queue.offer(n.left);}
if (n.right != null){queue.offer(n.right);}
}
}
return depth;
}
}
Leetcode. 111 二叉树的最小深度
class Solution {
public int minDepth(TreeNode root) {
// 找最先出现叶节点的层
if (root == null){return 0;}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int depth = 0;
while (!queue.isEmpty()){
int len = queue.size();
depth += 1;
for (int i = 0; i < len; i++){
TreeNode n = queue.peek();
queue.poll();
if (n.left == null && n.right == null){
return depth;
} else {
if (n.left != null){queue.offer(n.left);}
if (n.right != null){queue.offer(n.right);}
}
}
}
return depth;
}
}
总结
从未一次做那么多题,就算有模板,套的也很累。
不过对二叉树的理解更加深刻了,其实二叉树之前一直是一知半解,经过了这次刷题,对递归遍历和层序遍历的理解更清晰了一点。