二叉树问题小结

118 阅读5分钟

二叉树

二叉树(Binary Tree)是一种特殊的树结构,每个节点最多有两个子节点,分别称为左子节点和右子节点。树中的一个节点称为根节点,没有父节点的节点称为叶子节点,而具有子节点的节点称为内部节点。

二叉树特性

二叉树可以具有不同的特性,包括:

  1. 满二叉树(Full Binary Tree): 在满二叉树中,每个节点都有零或两个子节点。每一层的节点都被完全填满,除了最后一层。
  2. 完全二叉树(Complete Binary Tree): 在完全二叉树中,除了最后一层外,其他层都是满的,且最后一层的节点尽可能地靠左排列。
  3. 平衡二叉树(Balanced Binary Tree): 平衡二叉树是指左子树和右子树的高度差不超过1的二叉树,它可以保证在最坏情况下的查找效率。
  4. 搜索二叉树(Binary Search Tree,BST):对搜索二叉树进行中序遍历,得到的节点值序列将是升序排列的。

二叉树算法解题思路

解决二叉树问题的思路通常可以总结为以下步骤:

  1. 理解问题: 仔细阅读问题,确保理解问题的要求、输入和输出。了解问题所涉及的二叉树的性质和节点之间的关系。
  2. 选择适当的遍历方法: 需要根据问题的特点选择合适的遍历方式,包括前序、中序、后序和层序遍历。
  3. 递归思想: 大部分二叉树问题可以通过递归解决。递归的关键是将问题划分为更小的子问题。在每个节点上,递归地处理根节点、左子树和右子树。
  4. 递归终止条件: 在编写递归函数时,要定义好终止条件,通常是节点为空(null)或者达到叶子节点。
  5. 处理子问题: 在递归函数中,考虑如何处理子问题的返回值。可能需要合并子问题的结果、做一些计算,或者根据问题要求判断是否继续递归。
  6. 记录状态: 如果需要在递归过程中记录状态(例如路径、深度等),确保递归函数的参数和返回值中都包含这些信息。
  7. 遍历时的条件判断: 在遍历的过程中,根据具体问题的要求,根据节点的值或其他条件进行判断和处理。
  8. 使用辅助数据结构: 有些问题需要使用辅助数据结构,例如队列(BFS)、栈(DFS)等,以便在遍历时记录状态或实现特定操作。
  9. 分治法或其他方法: 有些问题可能可以使用分治法、动态规划等其他方法解决,取决于问题的性质。
  10. 测试和调试: 编写完代码后,务必测试多个测试用例,包括边界情况,确保代码的正确性。

常见的树的遍历方式

  1. 深度优先遍历(DFS)
//树的定义
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}

public class BinaryTreeTraversal {
    // 前序遍历(根-左-右)
    public void preorderTraversal(TreeNode root) {
        if (root == null) {
            return;
        }
        System.out.print(root.val + " ");
        preorderTraversal(root.left);
        preorderTraversal(root.right);
    }
    
    // 中序遍历(左-根-右)
    public void inorderTraversal(TreeNode root) {
        if (root == null) {
            return;
        }
        inorderTraversal(root.left);
        System.out.print(root.val + " ");
        inorderTraversal(root.right);
    }
    
    // 后序遍历(左-右-根)
    public void postorderTraversal(TreeNode root) {
        if (root == null) {
            return;
        }
        postorderTraversal(root.left);
        postorderTraversal(root.right);
        System.out.print(root.val + " ");
    }

    public static void main(String[] args) {
        // 创建一个示例二叉树
        TreeNode root = new TreeNode(1);
        // ... 构造树结构
        
        BinaryTreeTraversal solution = new BinaryTreeTraversal();
        System.out.print("Preorder traversal: ");
        solution.preorderTraversal(root);
        
        System.out.print("\nInorder traversal: ");
        solution.inorderTraversal(root);
        
        System.out.print("\nPostorder traversal: ");
        solution.postorderTraversal(root);
    }
}

  1. 广度优先遍历
import java.util.LinkedList;
import java.util.Queue;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}

public class BinaryTreeTraversal {
    // 广度优先遍历
    public void breadthFirstTraversal(TreeNode root) {
        if (root == null) {
            return;
        }

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            System.out.print(node.val + " ");
            
            if (node.left != null) {
                queue.offer(node.left);
            }
            if (node.right != null) {
                queue.offer(node.right);
            }
        }
    }

    public static void main(String[] args) {
        // 创建一个示例二叉树
        TreeNode root = new TreeNode(1);
        // ... 构造树结构
        
        BinaryTreeTraversal solution = new BinaryTreeTraversal();
        System.out.println("Depth-first traversal:");
        solution.depthFirstTraversal(root);
        
        System.out.println("\nBreadth-first traversal:");
        solution.breadthFirstTraversal(root);
    }
}

例题分析

  1. 872. 叶子相似的树

"叶子相似的树"问题是指给定两棵二叉树,判断它们的叶子节点序列是否相似。也就是说,如果两棵树的叶子节点序列相同,那么这两棵树就被认为是叶子相似的。使用深度遍历找到叶子节点就很好解决,以下是带注释的二叉树右视图实现的 Java 示例代码:

import java.util.ArrayList;
import java.util.List;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}

public class LeafSimilarTreesDFS {
    public boolean leafSimilar(TreeNode root1, TreeNode root2) {
        List<Integer> leaves1 = new ArrayList<>();
        List<Integer> leaves2 = new ArrayList<>();
        
        // 获取第一棵树的叶子节点值列表
        getLeaves(root1, leaves1);
        // 获取第二棵树的叶子节点值列表
        getLeaves(root2, leaves2);
        
        // 比较两棵树的叶子节点值列表是否相同
        return leaves1.equals(leaves2);
    }
    
    // 递归函数:获取树的叶子节点值列表
    private void getLeaves(TreeNode root, List<Integer> leaves) {
        if (root == null) {
            return;
        }
        
        // 如果是叶子节点,将节点值添加到列表中
        if (root.left == null && root.right == null) {
            leaves.add(root.val);
        }
        
        // 递归处理左子树和右子树
        getLeaves(root.left, leaves);
        getLeaves(root.right, leaves);
    }

    public static void main(String[] args) {
        TreeNode root1 = new TreeNode(3);
        // ... 构造第一棵树
        
        TreeNode root2 = new TreeNode(3);
        // ... 构造第二棵树
        
        LeafSimilarTreesDFS solution = new LeafSimilarTreesDFS();
        boolean isSimilar = solution.leafSimilar(root1, root2);
        System.out.println("Are the trees leaf-similar? " + isSimilar);
    }
}
  1. 199. 二叉树的右视图

二叉树的右视图是指从二叉树的右侧观察它,获取每一层最右边的节点。实现这个功能可以使用广度优先搜索(BFS),在每一层从右往左遍历并记录最右边的节点。以下是带注释的二叉树右视图实现的 Java 示例代码:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}

public class BinaryTreeRightView {
    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 levelSize = queue.size(); // 当前层的节点数
            TreeNode lastNode = null;

            // 遍历当前层的节点
            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.poll();
                lastNode = node; // 记录最右边的节点

                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }

            // 将最右边的节点的值添加到结果列表
            if (lastNode != null) {
                result.add(lastNode.val);
            }
        }

        return result;
    }

    public static void main(String[] args) {
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.right = new TreeNode(3);
        root.left.right = new TreeNode(5);
        root.right.right = new TreeNode(4);

        BinaryTreeRightView solution = new BinaryTreeRightView();
        List<Integer> rightView = solution.rightSideView(root);
        System.out.println("Right view of the binary tree: " + rightView);
    }
}

  1. 450. 删除二叉搜索树中的节点 删除二叉搜索树中的节点涉及到不同的情况,因为需要保持二叉搜索树的性质。以下是带有注释的 Java 实现,用于删除二叉搜索树中的节点:
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}

public class DeleteNodeInBST {
    public TreeNode deleteNode(TreeNode root, int key) {
        if (root == null) {
            return null;
        }

        // 寻找待删除节点
        if (key < root.val) {
            root.left = deleteNode(root.left, key);
        } else if (key > root.val) {
            root.right = deleteNode(root.right, key);
        } else { // 找到待删除节点
            if (root.left == null) {
                return root.right; // 没有左子树,直接返回右子树
            } else if (root.right == null) {
                return root.left; // 没有右子树,直接返回左子树
            }

            // 有两个子节点的情况,找到右子树的最小节点(后继节点)
            TreeNode successor = findMin(root.right);
            root.val = successor.val; // 将后继节点的值复制到待删除节点
            root.right = deleteNode(root.right, successor.val); // 在右子树中删除后继节点
        }

        return root;
    }

    // 寻找以 root 为根的二叉搜索树的最小节点
    private TreeNode findMin(TreeNode root) {
        while (root.left != null) {
            root = root.left;
        }
        return root;
    }

    public static void main(String[] args) {
        TreeNode root = new TreeNode(5);
        root.left = new TreeNode(3);
        root.right = new TreeNode(6);
        root.left.left = new TreeNode(2);
        root.left.right = new TreeNode(4);
        root.right.right = new TreeNode(7);

        DeleteNodeInBST solution = new DeleteNodeInBST();
        int key = 3;
        TreeNode newRoot = solution.deleteNode(root, key);
        System.out.println("Binary search tree after deleting node " + key);
        // 在此添加代码以打印新的二叉搜索树
    }
}

小结

解决二叉树问题需要深刻理解问题的要求和树的性质,合理使用遍历、递归、辅助数据结构等技巧来处理问题。在处理问题时,清晰的思路、代码的模块化和测试都是非常重要的。随着不断练习,您将更熟悉这些技巧,并能更轻松地解决各种类型的二叉树问题。