GESP2026年6月认证C++六级( 第三部分编程题(2、满二叉树))精讲




这是一道非常典型的树形 DFS + 树形 DP(后序递归)题。

有的同学看到都会觉得:

"这不是二叉树吗?是不是要 BFS?"

其实不是。

真正的突破口只有一句话:

一个节点是不是满二叉树,取决于它的左右子树。

看到这句话,应该可以想到:

后序 DFS!

今天我还是按照我的风格,用故事把它讲透,让同学们以后遇到"树形 DP"知道该怎么思考。


🌳 第一幕:满二叉树王国

1、很久很久以前。

(1)森林里住着很多小树精灵。

每个精灵都有两个孩子的位置:

复制代码
左孩子
右孩子

(2)但是:

有的孩子存在。

有的孩子不存在。

例如:

复制代码
      A
     / \
    B   C
左孩子   右孩子

(3)现在国王发布任务:

统计整片森林里,有多少棵"满二叉树"。

注意:

不是只有整棵树。

而是:

每一个节点,都算是对应一棵子树。


🌳 第二幕:什么叫满二叉树?

1、有的同学容易和:

  • 完全二叉树

  • 平衡二叉树

混淆。

所以先讲清楚。


2、第一种

复制代码

(1)只有一个点。

是不是满二叉树?


(2)答案:

是!


(3)因为:

叶子都一样高。

没有违反规则。


3、第二种

复制代码
      ○
     / \
    ○   ○

是不是满二叉树?

也是。


4、第三种

复制代码
        ○
      /   \
     ○     ○
    / \   / \
   ○ ○   ○ ○

是不是满二叉树?

也是。


5、那什么时候不是?

(1)例如:

复制代码
      ○
     /
    ○

不是。


(2)为什么?

因为:

根只有一个孩子。

违反:

每个非叶子,必须要有两个孩子。


(3)还有:

复制代码
      ○
     / \
    ○   ○
       /
      ○

也不是。


(4)为什么?

因为:

所有叶子深度不同。


6、所以:

满二叉树必须同时满足:

① 每个非叶子都有两个孩子

② 所有叶子深度相同


🌳 第三幕:暴力可以吗?

1、这道题节点数为:

复制代码
100000

对于每个节点:

重新遍历一次子树。

复杂度:

复制代码
O(n²)

一定超时。

怎么办?


2、有没有办法:

每个节点只访问一次?

答案:

DFS!


🌳 第四幕:老师的小侦探

1、我们来到某个节点:

复制代码
        A
       / \
      B   C

2、汉克老师问:

A是不是满二叉树?

(1)能直接知道吗?

不知道。


(2)必须先问:

复制代码
B

是不是?

(3)再问:

复制代码
C

是不是?

(4)所以:

必须:

复制代码
先孩子

后爸爸

3、这就是:

后序遍历


顺序:

复制代码
左

↓

右

↓

自己

🌳 第五幕:每个节点要记录什么?

1、有的同学第一次做树DP,

总想记录很多东西。

其实这里只需要两个信息。


2、第一个

复制代码
高度

记:

复制代码
h[u]

表示:

以u为根,高度是多少。


(1)例如:

复制代码

高度:

复制代码
1

(2)例如:

复制代码
      ○
     / \
    ○   ○

高度:

复制代码
2

3、怎么算高度?

(1)孩子高度已经知道了。

所以:

复制代码
h[u]

=

max(

左孩子高度

右孩子高度

)+1

(2)这是不是很熟?

因为:

DFS回来以后,

孩子信息已经有了。


🌳 第六幕:第二个信息

1、记录:

复制代码
chk[u]

表示:

以u为根是不是满二叉树。

只有:

复制代码
true

false

2、那么:

什么时候true?

汉克老师继续问:

根:

复制代码
A

成为满二叉树,

必须满足什么?


3、第一:

左边必须已经是满二叉树。

复制代码
chk[left]

==true

4、第二:

右边也是。

复制代码
chk[right]

==true

5、第三:

左右高度一样。

(1)例如:

复制代码
      ○
     / \
    ○   ○

左高:

复制代码
2

右高:

复制代码
2

可以。


(2)如果:

复制代码
      ○
     / \
    ○   ○
   /
  ○

左:

复制代码
3

右:

复制代码
2

叶子高度不同。

不是满二叉树。


6、于是:

最后得到判断公式:

复制代码
chk[u]

=

chk[left]

&&

chk[right]

&&

h[left]==h[right]

是不是特别全面?

这是问题的核心判断。


🌳 第七幕:为什么叶子天然就是满二叉树?

1、来看:

复制代码

(1)左右都是:

复制代码
null

(2)DFS到:

复制代码
null

直接返回。


(3)于是:

复制代码
左高度

0

右高度:

复制代码
0

相等。

所以:

复制代码
chk=true

2、因此:

所有叶子,

天然都是满二叉树。

答案一定至少有:

复制代码
叶子数量

棵。


🌳 第八幕:整个DFS流程

1、例如:

复制代码
        1
      /   \
     2     3
    / \
   4   5

(1)我们从根开始:

复制代码
dfs(1)

(2)先去左。

复制代码
dfs(2)

(3)继续:

复制代码
dfs(4)

(4)4没有孩子。

回来。


(5)得到:

复制代码
h[4]=1

chk[4]=true

(6)继续:

复制代码
dfs(5)

回来:

复制代码
h[5]=1

chk[5]=true

(7)现在:

节点2终于可以计算:

左高:

复制代码
1

右高:

复制代码
1

一样。

于是:

复制代码
h[2]=2

chk[2]=true

(8)继续:

节点3。

是叶子。

复制代码
h=1

chk=true

(9)最后:

节点1。

左:

复制代码
2

右:

复制代码
1

不同。


所以:

复制代码
chk[1]=false

(10)于是有:

复制代码
4

5

3

2

共:

复制代码
4

棵。


🌳 第九幕:为什么叫树形DP?

1、有的同学问:

汉克老师。

这里只有DFS呀?

为什么你说这是树形DP呢?


2、因为动态规划本质:

就是:

大问题依赖小问题。


(1)这里:

复制代码
节点1

依赖:

复制代码
节点2

节点3

(2)节点2又依赖:

复制代码
节点4

节点5

是不是:

和DP是一模一样的?


3、而且状态长在树上。

所以叫:

树形DP。


🌳 第十幕:参考程序

复制代码
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 100010;

int n;
int leftChild[N], rightChild[N];

// h[i]:以 i 为根的子树高度
int h[N];

// full[i]:以 i 为根是否为满二叉树
bool full[N];

// 答案
int ans = 0;

void dfs(int u)
{
    if (u == 0) return;   // 空节点

    // 后序遍历:先左右子树
    dfs(leftChild[u]);
    dfs(rightChild[u]);

    // 计算高度
    h[u] = max(h[leftChild[u]], h[rightChild[u]]) + 1;

    // 判断是否为满二叉树
    full[u] = full[leftChild[u]]
           && full[rightChild[u]]
           && (h[leftChild[u]] == h[rightChild[u]]);

    if (full[u])
        ans++;
}

int main()
{
    cin >> n;

    // 空树看作满二叉树,便于递推
    full[0] = true;

    for (int i = 1; i <= n; i++)
        cin >> leftChild[i] >> rightChild[i];

    dfs(1);

    cout << ans << endl;

    return 0;
}

🌟 思维导图

复制代码
             满二叉树统计
                    │
                    ▼
          每个节点统计一次
                    │
                    ▼
                 后序DFS
                    │
      ┌─────────────┴─────────────┐
      ▼                           ▼
记录高度 h[u]               是否满 chk[u]
      │                           │
      ▼                           ▼
h=max(左,右)+1      左满 && 右满 && 左高==右高
      │                           │
      └─────────────┬─────────────┘
                    ▼
             如果成立,答案+1

🌈 举一反三

如果一个节点的答案,需要依赖左右子树的答案,那就可以想到"后序 DFS + 树形 DP"。

以后遇到下面这些经典问题时,都可以尝试下同样的方法:

  • 判断平衡二叉树

  • 求二叉树高度

  • 求二叉树直径

  • 求最大独立集

  • 求树上最长路径

  • 判断各种子树性质


它们有一个共同特点:

先计算孩子,再计算父亲。


相关推荐
踮起脚看烟花3 小时前
多人聊天室实现v2.0
c++·信息与通信
梦帮科技3 小时前
UE5 GAS 实战:用 Gameplay Ability System 搭建「赛博修真」境界与技能体系
c++·人工智能·python·ue5·c#
旖-旎3 小时前
QT系统篇(5)(下)
开发语言·c++·qt
99乘法口诀万物皆可变3 小时前
PcanToVectorXL_V01:打通 Vector 与 PCAN 的双向 CAN/CAN‑FD 桥梁
c++·学习
liulun3 小时前
C++ WinRT中的事件
开发语言·c++
whitelbwwww4 小时前
c++运行onnx模型
开发语言·c++
C路在脚下5 小时前
HSMS 连接总失败?排查这 5 个配置点
c++·嵌入式硬件
郝学胜_神的一滴5 小时前
Qt 高级编程 034:深耕QWidget底层内核—彻底吃透无边框窗口设计核心原理
c++·qt
QiLinkOS5 小时前
第三视觉理解徐玉生与他的商业活动(26)
大数据·c++·人工智能·算法·开源协议