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"。

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

  • 判断平衡二叉树

  • 求二叉树高度

  • 求二叉树直径

  • 求最大独立集

  • 求树上最长路径

  • 判断各种子树性质


它们有一个共同特点:

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


相关推荐
AC赳赳老秦3 小时前
采集行为合规自检:OpenClaw 自动校验 robots 协议与采集频率,规避违规采集风险
java·开发语言·c++·python·php·deepseek·openclaw
无名猿3 小时前
C++ 查找算法:std::find 与容器成员 find 的性能差距
c++·性能优化·stl·标准库
程序猿编码4 小时前
工业视觉多任务方案:可编程梯度信息视觉框架,训练评估 + C++ 部署完整实战
开发语言·c++·视觉·推理引擎
朝朝辞暮i5 小时前
C++ 第 21 课:struct —— 把一组相关数据打包在一起
开发语言·c++·算法
SHARK_pssm6 小时前
【C++——类和对象(下)】
开发语言·c++·经验分享·笔记
汉克老师6 小时前
GESP2026年9月认证C++二级( 第一部分选择题(1~7题)精讲
c++·gesp·小学生·学c++编程
沫璃染墨7 小时前
《从零入门Linux系统篇(五十四):线程篇·七——互斥锁底层原理:从原子交换到线程竞争与锁实现》
linux·运维·服务器·开发语言·c++·驱动开发·系统架构
可乐ea8 小时前
GitHub Security Lab 开源 Fuzzing Taskflow:让 LLM 智能体自己跑完 C/C++ 模糊测试
jvm·c++·github·模糊测试·mcp·代码agent·智能体自动化
朝朝辞暮i8 小时前
C++ 第 13 课:值传递 —— 为什么函数里改了,外面却没变?
java·c++·算法
无忧.芙桃10 小时前
C++语言原理与实践(十):vector类的底层实现
开发语言·c++·算法