【题目链接】
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的左子树对称
三、问题求解
- 先求出二叉树中各子树的结点数
- 深度优先(先序)遍历二叉树,判断每个子树是否是对称二叉树。
- 如果发现一棵子树是对称二叉树,则结果变量ans记录该二叉树的结点数。
- 否则继续分别判断当前树的左右子树是否是对称二叉树。
- 最优性剪枝:如果当前子树的结点数小于等于已经求出的结果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=1∗20+2∗21+3∗22...+h∗2h−1
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=1∗21+2∗22+...+(h−1)2h−1+h∗2h
二者相减,得:
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=h∗2h−(20+21+22+...+2h−1)=h∗2h−2−11(2h−1)=(h−1)∗2h+1
已知
h
≈
log
2
n
h\approx \log_2 n
h≈log2n
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=(log2n−1)∗2log2n+1=nlog2n−n+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;
}