二叉树
二叉树(Binary Tree)是一种特殊的树结构,每个节点最多有两个子节点,分别称为左子节点和右子节点。树中的一个节点称为根节点,没有父节点的节点称为叶子节点,而具有子节点的节点称为内部节点。
二叉树特性
二叉树可以具有不同的特性,包括:
- 满二叉树(Full Binary Tree): 在满二叉树中,每个节点都有零或两个子节点。每一层的节点都被完全填满,除了最后一层。
- 完全二叉树(Complete Binary Tree): 在完全二叉树中,除了最后一层外,其他层都是满的,且最后一层的节点尽可能地靠左排列。
- 平衡二叉树(Balanced Binary Tree): 平衡二叉树是指左子树和右子树的高度差不超过1的二叉树,它可以保证在最坏情况下的查找效率。
- 搜索二叉树(Binary Search Tree,BST):对搜索二叉树进行中序遍历,得到的节点值序列将是升序排列的。
二叉树算法解题思路
解决二叉树问题的思路通常可以总结为以下步骤:
- 理解问题: 仔细阅读问题,确保理解问题的要求、输入和输出。了解问题所涉及的二叉树的性质和节点之间的关系。
- 选择适当的遍历方法: 需要根据问题的特点选择合适的遍历方式,包括前序、中序、后序和层序遍历。
- 递归思想: 大部分二叉树问题可以通过递归解决。递归的关键是将问题划分为更小的子问题。在每个节点上,递归地处理根节点、左子树和右子树。
- 递归终止条件: 在编写递归函数时,要定义好终止条件,通常是节点为空(null)或者达到叶子节点。
- 处理子问题: 在递归函数中,考虑如何处理子问题的返回值。可能需要合并子问题的结果、做一些计算,或者根据问题要求判断是否继续递归。
- 记录状态: 如果需要在递归过程中记录状态(例如路径、深度等),确保递归函数的参数和返回值中都包含这些信息。
- 遍历时的条件判断: 在遍历的过程中,根据具体问题的要求,根据节点的值或其他条件进行判断和处理。
- 使用辅助数据结构: 有些问题需要使用辅助数据结构,例如队列(BFS)、栈(DFS)等,以便在遍历时记录状态或实现特定操作。
- 分治法或其他方法: 有些问题可能可以使用分治法、动态规划等其他方法解决,取决于问题的性质。
- 测试和调试: 编写完代码后,务必测试多个测试用例,包括边界情况,确保代码的正确性。
常见的树的遍历方式
- 深度优先遍历(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);
}
}
- 广度优先遍历
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);
}
}
例题分析
"叶子相似的树"问题是指给定两棵二叉树,判断它们的叶子节点序列是否相似。也就是说,如果两棵树的叶子节点序列相同,那么这两棵树就被认为是叶子相似的。使用深度遍历找到叶子节点就很好解决,以下是带注释的二叉树右视图实现的 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);
}
}
二叉树的右视图是指从二叉树的右侧观察它,获取每一层最右边的节点。实现这个功能可以使用广度优先搜索(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);
}
}
- 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);
// 在此添加代码以打印新的二叉搜索树
}
}
小结
解决二叉树问题需要深刻理解问题的要求和树的性质,合理使用遍历、递归、辅助数据结构等技巧来处理问题。在处理问题时,清晰的思路、代码的模块化和测试都是非常重要的。随着不断练习,您将更熟悉这些技巧,并能更轻松地解决各种类型的二叉树问题。