问题描述
给定两棵二叉树的根节点 p 和 q,编写一个函数来检验这两棵树是否相同。若两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
方法一:深度优先搜索(DFS)
思路与算法
深度优先搜索(DFS)是一种常见的遍历树的方法,通过递归实现。DFS 的基本思路如下:
1. 基础情况:
- 如果两个节点都为空,则认为它们是相同的。
- 如果其中一个节点为空,另一个不为空,则认为它们不同。
- 如果两个节点的值不同,则认为它们不同。2. 递归检查:
- 递归检查左子树:isSameTree(p.left, q.left)。
- 递归检查右子树:isSameTree(p.right, q.right)。
代码实现
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if (p == nullptr && q == nullptr) {
return true;
} else if (p == nullptr || q == nullptr) {
return false;
} else if (p->val != q->val) {
return false;
} else {
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
}
};
复杂度分析
- 时间复杂度:O(min(m, n)),其中 m 和 n 分别是两棵二叉树的节点数。我们对两棵树进行递归遍历,当节点不相同时立即返回 false,访问的节点数不会超过较小的二叉树的节点数。
- 空间复杂度:O(min(m, n)),取决于递归调用的栈深度,最坏情况下,栈深度等于较小树的高度。
方法二:广度优先搜索(BFS)
思路与算法
广度优先搜索(BFS)是一种逐层遍历树的算法。我们可以使用两个队列来实现 BFS:
1. 初始化:
- 如果两棵树都为空,则它们相同,返回 true。
- 如果其中一个为空,另一个不为空,则它们不同,返回 false。2. 队列操作:
- 使用两个队列分别存储两个二叉树的节点,初始时将根节点加入队列。
- 每次从两个队列中各取出一个节点进行比较。
- 如果节点的值不同,则返回 false。
- 如果左孩子或者右孩子的结构不同(一个为空,另一个不为空),则返回 false。这里使用异或操作 ^ 判断其中一个节点为空而另一个不为空的情况,简化了代码逻辑。
- 将非空的子节点分别加入队列。3. 结束条件:
- 当两个队列同时为空时,说明两棵树相同,返回 true。
代码实现
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if (p == nullptr && q == nullptr) {
return true;
} else if (p == nullptr || q == nullptr) {
return false;
}
queue<TreeNode*> queue1, queue2;
queue1.push(p);
queue2.push(q);
while (!queue1.empty() && !queue2.empty()) {
TreeNode* node1 = queue1.front(); queue1.pop();
TreeNode* node2 = queue2.front(); queue2.pop();
if (node1->val != node2->val) {
return false;
}
TreeNode* left1 = node1->left, *right1 = node1->right;
TreeNode* left2 = node2->left, *right2 = node2->right;
if ((left1 == nullptr) ^ (left2 == nullptr)) {
return false;
}
if ((right1 == nullptr) ^ (right2 == nullptr)) {
return false;
}
if (left1 != nullptr) queue1.push(left1);
if (right1 != nullptr) queue1.push(right1);
if (left2 != nullptr) queue2.push(left2);
if (right2 != nullptr) queue2.push(right2);
}
return queue1.empty() && queue2.empty();
}
};
复杂度分析
- 时间复杂度:O(min(m, n)),我们对两个树进行同步 BFS,当节点不相同时立即返回 false,遍历的节点数不会超过较小树的节点数。
- 空间复杂度:O(min(m, n)),取决于队列中存储的节点数量,最坏情况下队列的大小等于较小树的节点数。
优化与不足
1. DFS 方法:如果树的高度较大,递归调用可能会导致栈溢出。在这种情况下,BFS 是一个更安全的选择。但是DFS代码少,简单。
2. BFS 方法:BFS 在遍历树时使用显式队列,避免了深度递归的栈深度问题,并且可以通过异或操作 ^ 简化判断左右子树结构是否相同的代码逻辑。但是BFS代码多,难度较大。3. 代码:代码大量使用 if 和else if,过于臃肿,不够优美。但是学习理解更加容易。
总结
在这类题目中,常用的遍历树的方法包括深度优先搜索(DFS)和广度优先搜索(BFS)。DFS 通常实现简单,但在处理深度大的树时需要注意栈溢出问题。BFS 则使用队列来实现,不存在栈溢出风险,代码可读性也较好。通过这两种方法,我们可以有效地判断两棵树是否相同。这类问题的核心是对树的遍历和对节点的比较,掌握这些技巧可以帮助我们解决许多类似的树问题。