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)    收藏  举报