信息学奥赛一本通 1981:【18NOIP普及组】对称二叉树 | 洛谷 P5018【NOIP2018 普及组】 对称二叉树

本文详细介绍了如何解决NOIP2018普及组的一道对称二叉树问题。通过定义一个`check()`函数判断两棵子树是否对称,结合递归遍历二叉树,实现判断整个二叉树的对称性。在算法中,首先计算每个子树的节点数,然后通过比较节点值、节点数以及左右子树的对称性来确定对称性。最终,通过`Search()`函数遍历所有子树,找到结点数最多的对称子树。代码实现清晰,易于理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【题目链接】

ybt 1981:【18NOIP普及组】对称二叉树
洛谷 P5018【NOIP2018 普及组】 对称二叉树

【题目考点】

1. 二叉树

【解题思路】

一、判断一个二叉树是否是对称二叉树:
  • 对称变换:将该树中所有结点的左右子树交换。显然,一棵树经过两次对称变换后,会变为原来的树。
  • 两树对称:如果树1经过对称变换可以变为树2,那么称树1与树2对称。显然,树1与树2对称时,树2与树1对称。
  • 如果一个二叉树是对称二叉树,那么其左子树应该与右子树对称。
    • 证明:二叉树是对称的,那么其经过对称变换后的左子树,是由原右子树变换过来的。已知对称变换后的树与原树相同,那么变换后的左子树与原左子树相同,那么原左子树与原右子树是对称的。
二. 判断两棵二叉树是否对称

设函数check(),参数为树1、树2两棵树的根,该函数判断树1、树2是否对称。

  • 判断两棵树是否对称:
    • 如果两棵树都是空树(根结点地址都为-1),那么它们是对称的。
    • 如果两棵树都不是空树,同时满足如下条件时,二者才是对称的
      • 两棵树根结点的权值应该相同
      • 两棵树的结点数应该相同
      • 两棵树的高度应该相同
      • 树1的左子树经过对称变换后与树2右子树相同,因此树1的左子树应该与树2右子树对称。
      • 树1的右子树经过对称变换后与树2左子树相同,树1的右子树应该与树2的左子树对称
三、问题求解
  1. 先求出二叉树中各子树的结点数
  2. 深度优先(先序)遍历二叉树,判断每个子树是否是对称二叉树。
    • 如果发现一棵子树是对称二叉树,则结果变量ans记录该二叉树的结点数。
    • 否则继续分别判断当前树的左右子树是否是对称二叉树。
  3. 最优性剪枝:如果当前子树的结点数小于等于已经求出的结果ans,那么基本该树是对称二叉树,结果ans也不会变得更小,可以进行剪枝。
四、时间复杂度证明

dfs过程为:对以每个结点为根的子树都要判断该树是否是对称二叉树。
对于一般的二叉树,如果一个结点的两个子树高度不同或结点数不同,则直接判断该树不是对称二叉树。
当一个结点的两棵子树结点数相同,高度相同,形态相同,且每对高度相同的子树形态都相同时,即当该树接近于满二叉树时,需要调用check的次数最多。
调用check函数,传入r1、r2,极端情况下可以认为会遍历到以r1为根子树以及以r2为根的子树的所有结点。
如果该树是满二叉树,树的高度 h h h O ( log ⁡ n ) O(\log n) O(logn)
对每个结点都判断该树是否是对称二叉树,而判断过程又要遍历子树中的每个结点。
因此访问结点的总次数为以每个结点为根的子树的结点数加和。
那么结点1被访问1次,第2层的结点被访问2次,第3层的结点被访问3次,。。。第h层的结点被访问h次,总访问次数即为总计算次数,设总计算次数为S,则:
S = 1 ∗ 2 0 + 2 ∗ 2 1 + 3 ∗ 2 2 . . . + h ∗ 2 h − 1 S=1*2^0+2*2^1+3*2^2...+h*2^{h-1} S=120+221+322...+h2h1
2 S = 1 ∗ 2 1 + 2 ∗ 2 2 + . . . + ( h − 1 ) 2 h − 1 + h ∗ 2 h 2S=1*2^1+2*2^2+...+(h-1)2^{h-1}+h*2^{h} 2S=121+222+...+(h1)2h1+h2h
二者相减,得:
S = h ∗ 2 h − ( 2 0 + 2 1 + 2 2 + . . . + 2 h − 1 ) = h ∗ 2 h − 1 ( 2 h − 1 ) 2 − 1 = ( h − 1 ) ∗ 2 h + 1 S=h*2^h-(2^0+2^1+2^2+...+2^{h-1})\\ =h*2^h-\dfrac{1(2^h-1)}{2-1}\\ =(h-1)*2^h+1 S=h2h(20+21+22+...+2h1)=h2h211(2h1)=(h1)2h+1
已知 h ≈ log ⁡ 2 n h\approx \log_2 n hlog2n
S = ( log ⁡ 2 n − 1 ) ∗ 2 log ⁡ 2 n + 1 = n log ⁡ 2 n − n + 1 S=(\log_2n-1)*2^{\log_2n}+1=n\log_2n-n+1 S=(log2n1)2log2n+1=nlog2nn+1
O ( S ) = O ( n log ⁡ n ) O(S)=O(n\log n) O(S)=O(nlogn)
因此该算法的时间复杂度最大为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

【题解代码】

解法1:二叉树
#include<bits/stdc++.h>
using namespace std;
#define N 1000005
struct Node
{
    int val, left, right, siz, dep;//val:权值, left:左孩子地址 right:右孩子地址 siz:以该结点为根的子树的结点数 dep:以该结点为根的子树的深度 
} tree[N];//tree[i],为id为i的结点
int n, ans;//ans:结点数量最大的对称子树的结点数
bool check(int r1, int r2)//判断以r1为根的子树和以r2为根的子树是否对称
{
    return r1 == -1 && r2 == -1 || //空结点,相同
			r1 != -1 && r2 != -1 && //左右子树都有结点
            tree[r1].val == tree[r2].val && //树根结点权值相同,
            tree[r1].siz == tree[r2].siz && //树结点数相同
            tree[r1].dep == tree[r2].dep && //树高度相同 
            check(tree[r1].left, tree[r2].right) && //经过对称变换后,root2树的右子树应该与root1树的左子树对称
            check(tree[r1].right, tree[r2].left);//经过对称变换后,root2树的左子树应该与root1树的右子树对称
}
void dfsInit(int r)//初始化树的各子树结点数和深度
{
	if(r == -1)
		return;
	dfsInit(tree[r].left);
	dfsInit(tree[r].right);
	tree[r].siz = tree[tree[r].left].siz+tree[tree[r].right].siz+1;
	tree[r].dep = max(tree[tree[r].left].dep, tree[tree[r].right].dep)+1;
} 
void dfs(int r)//确定结点最多的对称的子树的结点数
{
	if(r == -1)//空结点 返回 
		return;
    if(tree[r].siz <= ans)//剪枝,如果当前子树的结点数已经比获得的最大对称子树结点数要小,那么没必要再搜索下去。
        return;
    if(check(tree[r].left, tree[r].right))//如果树r是对称的,那么其左右子树对称
        ans = tree[r].siz;//如果对称,记录结点数。由于已有剪枝操作,此时siz[r]一定大于ans
    else
    {
        dfs(tree[r].left);
        dfs(tree[r].right);
    }
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
    cin >> n;
    for(int i = 1; i <= n; ++i)
    	cin >> tree[i].val;
    for(int i = 1; i <= n; ++i)
    	cin >> tree[i].left >> tree[i].right;
    dfsInit(1);
    dfs(1);//从根结点开始,遍历所有子树,找出对称子树,并看其结点数,保存最大结点数
    cout << ans;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值