Luogu P5018 [NOIP 2018 普及组] 对称二叉树 人生第一道AC的绿题
学习罗勇军、郭卫斌老师的《算法竞赛》后找到这道题。人生中第一道AC的绿题献给二叉树了ouo
(一道题写了一个多小时才写明白,写的过程中出现了很多次问题,包括数组越界等。)
题目编号
思路
因为是从书上的练习题部分找到的这道题,思路上受了一定的影响,先入为主的想去用二叉树的递归遍历来做。
使用递归遍历来做的话,我首先想到了一个错误解法:对这个二叉树进行左子树-右子树-根和右子树-左子树-根的遍历。如果这两个遍历序列完全相等,表明这个二叉树是对称的。这个思路问题在于,一个后序遍历并无法唯一确定一个二叉树,因此两个遍历序列相等 $\Rightarrow$ 二叉树是对称二叉树但是二叉树是对称二叉树 $\nRightarrow$ 两个遍历序列相等。
不过,递归遍历的做法还是有用处的,可以通过递归来计算出二叉树的结点个数。准备一个 int 类型的容器来存储每个结点的子树的结点个数(包含上这个结点),按照递归的思想写出来一个函数,就可以将每个结点的子树的结点个数求出来。
示例代码:
struct TNode {
int weight;
int l, r;
int size = 0;
};
void dfs(int root, vector<TNode>& trees) {
if (root == -1)
return;
dfs(trees[root].l, trees); // 递归求得左子树的结点数
dfs(trees[root].r, trees); // 递归求得右子树的结点数
int lchild = trees[root].l;
int rchild = trees[root].r;
// 注意防止索引为 -1 导致的数组越界
int lsize = lchild == -1 ? 0 : trees[lchild].size;
int rsize = rchild == -1 ? 0 : trees[rchild].size;
trees[root].size = 1 + lsize + rsize; // 根节点加上左右子树的结点数
}
这里我定义了一个结构体来存储相关数据。
知道了每个结点的子树的大小,现在需要解决的问题就是满足对称二叉树的最大的子树有多大?
我们可以发现,使用递归的思想是可以实现判断一个树是否为对称二叉树。
思路如下:输入 l, r 两个变量,分别代表两棵树的根节点。而函数的返回值代表的是这棵树是否为对称二叉树。如下图,如果该二叉树是对称二叉树,颜色相等的子树拼在一起也应当分别是一棵对称二叉树。反之,颜色相等的子树拼在一起都分别是一棵对称二叉树,且左子树和右子树的根节点权值相等,则该二叉树是一棵对称二叉树。

示例代码:
bool check(int l, int r, vector<TNode>& trees) {
if (l == -1 && r == -1)
return true;
else if (l == -1 ^ r == -1)
return false;
else if (trees[l].size != trees[r].size)
return false;
else if (trees[l].weight != trees[r].weight)
return false;
int llchild = trees[l].l;
int lrchild = trees[l].r;
int rlchild = trees[r].l;
int rrchild = trees[r].r;
return check(llchild, rrchild, trees) && check(lrchild, rlchild, trees);
}
至此就很容易就能求得答案了。只需要遍历存储每个结点的子树的大小的数组,使用满足对称二叉树的性质的值来更新最大值即可。
完整代码:
#include <iostream>
#include <vector>
using namespace std;
struct TNode {
int weight;
int l, r;
int size = 0;
};
void dfs(int root, vector<TNode>& trees) {
if (root == -1)
return;
dfs(trees[root].l, trees); // 递归求得左子树的结点数
dfs(trees[root].r, trees); // 递归求得右子树的结点数
int lchild = trees[root].l;
int rchild = trees[root].r;
// 注意防止索引为 -1 导致的数组越界
int lsize = lchild == -1 ? 0 : trees[lchild].size;
int rsize = rchild == -1 ? 0 : trees[rchild].size;
trees[root].size = 1 + lsize + rsize; // 根节点加上左右子树的结点数
}
bool check(int l, int r, vector<TNode>& trees) {
if (l == -1 && r == -1)
return true;
else if (l == -1 ^ r == -1)
return false;
else if (trees[l].size != trees[r].size)
return false;
else if (trees[l].weight != trees[r].weight)
return false;
int llchild = trees[l].l;
int lrchild = trees[l].r;
int rlchild = trees[r].l;
int rrchild = trees[r].r;
return check(llchild, rrchild, trees) && check(lrchild, rlchild, trees);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int n;
cin >> n;
vector<TNode> trees(n + 1);
for (int i = 1; i <= n; ++i) {
cin >> trees[i].weight;
}
for (int i = 1; i <= n; ++i) {
cin >> trees[i].l >> trees[i].r;
}
dfs(1, trees);
int max = 0;
for (int i = 1; i <= n; ++i) {
if (trees[i].size > max && check(trees[i].l, trees[i].r, trees))
max = trees[i].size;
}
cout << max;
}
有关时间复杂度
dfs(1, trees); 函数执行的过程中,二叉树的每一个结点子树的大小都进行了一次计算,对应可知时间复杂度为 $O(n)$。
下面的遍历求得最大值的代码块,最坏情况下需要对每个节点都使用 check 函数递归求解其是否为对称二叉树,对应的时间复杂度为 $O(n\log_2 n)$。
综合来看,算法的时间复杂度为 $O(n\log_2 n)$。
posted on 2026-03-24 19:52 SleePerwtm 阅读(0) 评论(0) 收藏 举报
浙公网安备 33010602011771号