🌳 树的入门 · 交互教程

从零开始学树 | 定义 · 术语 · 存储 · 遍历 · 深度 · 距离

GESP 六级前置知识 · 五年级友好

0什么是树?

树不是「长在地上的植物」,而是一种特殊的图。它长这样(下面这棵就是本课的演示树,9 个点):

树的三个定义(满足任意两个就能推出第三个)

最重要的性质:树里任意两个点之间有且只有一条简单路径。这一点让「两点距离」有了唯一答案,树的题目都离不开它。

🔍 判定小测试:下面这些图,哪些是树?(点选项看对错)

这个图是树吗?(3 个点 3 条边,围成三角形)

✔️ 不是!三角形有「环」——1→2→3→1 绕一圈回来了。树不能有环。

一条链(4 个点 3 条边排成一行)呢?

✔️ 是树!连通、无环、4 点 3 边,全满足。链是最朴素的树。

一颗星星(中心 + 3 片叶子,4 点 3 边)?

✔️ 是树!星星、链、Y 字形……都是树,只要连通无环就行。

两个分开的三角形(6 个点 6 条边)?

✔️ 不是!它根本不连通——左边到不了右边。虽然每个小三角形都是 3 点 3 边,但整张图不是树。

一个正方形(4 点 4 边围成圈)?

✔️ 不是!4 点 4 边 = 多了一条边,围成了环。树必须是 n 点 n−1 边。
🎯 记法:树 = 连通 + 无环 = 任意两点只有一条路径 = n 点 n−1 边。看到题目说「n 个点,n−1 条边」→ 立刻想到树!

1树的术语(点一点就懂)

我们可以任选一个点当「根」(就像把树拎起来)。选 1 号点当根后,每个点就有了爸爸、孩子、深度这些身份。点击下面树上的任意节点,看它的「身份档案」!

选中的节点 祖先(含根) 孩子
👆 点一个节点试试!
术语意思在这棵树上
我们选来「挂」整棵树的点1
父节点 / 孩子深度差 1、直接相连、往根的方向是父2 的父是 1,2 的孩子是 4、5
叶子没有孩子的节点4、7、8、9
深度到根的距离(根 = 0)5 的深度是 2
祖先自己到根路径上的点(不含自己)8 的祖先是 5、2、1
子树自己 + 所有后代2 的子树是 {2,4,5,7,8}
⚠️ 根可以随便选!选不同的根,爸爸和孩子会变,但「两点间距离」永远不会变。

2树的存储:邻接表

程序里怎么存树?最常用邻接表:每个点开一个列表,记下所有和它相连的点。

#include <bits/stdc++.h>
using namespace std;

const int N = 200005;
vector<int> g[N];      // g[u] 里存所有和 u 相连的点(邻居)

int main() {
    int n;
    cin >> n;
    for (int i = 1; i < n; i++) {   // 树有 n-1 条边
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);          // u 和 v 相连
        g[v].push_back(u);          // 无向,双向都记
    }
    // 例:输出节点 2 的所有邻居
    for (int i = 0; i < (int)g[2].size(); i++) cout << g[2][i] << " ";
    return 0;
}
💡 二叉树特例:如果题目保证每个点最多两个孩子,也可以直接用 lc[i]rc[i] 两个数组存左右孩子,更省空间。

3树的遍历:DFS 前序 & BFS 层序

遍历 = 把每个点都走一遍。两种走法都要会(GESP 六级直接考):

动画①:前序遍历(DFS)

已访问 已出栈(子树走完) 当前节点
调用栈:
前序序列:(还没开始)

动画②:层序遍历(BFS)

颜色深浅 = 层数,越远越暖色。队头出队,把孩子入队,波纹式扩散。

第 0 层(根) 第 1 层 第 2 层 第 3 层 当前
队列:
层序序列:(还没开始)

代码对照

// 前序遍历(DFS 递归):先自己,再孩子
void preorder(int u, int fa) {
    cout << u << " ";                    // 访问自己
    for (int v : g[u]) {
        if (v == fa) continue;          // 别走回爸爸
        preorder(v, u);                 // 递归访问孩子
    }
}

// 层序遍历(BFS 队列):一层一层
void levelorder() {
    queue<int> q;
    q.push(1);                          // 根入队
    vis[1] = true;
    while (!q.empty()) {
        int u = q.front(); q.pop();
        cout << u << " ";               // 访问自己
        for (int v : g[u]) {
            if (vis[v]) continue;
            vis[v] = true;
            q.push(v);                  // 孩子入队
        }
    }
}
💡 遍历能顺手算出深度:depth[v] = depth[u] + 1 加进上面的代码,一次遍历就得到所有点的深度——非常常用,一定要记住!

4深度与染色:树是「二部图」

给树染两种颜色:偶数深度的点染蓝色,奇数深度的点染橙色。你马上会发现一个超强规律。

偶数深度(蓝) 奇数深度(橙) 当前
🏁 规律:树上任何相邻的两个点,颜色一定不同!(因为走一条边,深度 +1,奇偶翻转。)这种能两色染好的图叫二部图(也叫二分图),树一定是二部图。

5路径与距离:LCA 的魔法公式

树上两点 u、v 的距离怎么算?先找到它们的最近公共祖先(LCA,Lowest Common Ancestor)——就是离它们俩都最近的公共祖先。然后套公式:

dist(u,v) = depth[u] + depth[v] − 2 × depth[LCA(u,v)]

👇 玩法:先点一个节点(绿色=起点),再点第二个节点(红色=终点),路径会亮起来,公式也会自动代入数字算给你看!

起点 终点 路径上的点 路径上的边
👆 点两个节点试试!
💡 试试点 5 和 9:LCA = 1,dist = depth[5]+depth[9]−0 = 2+3 = 5(奇数)→ 5 和 9 之间只能奇数步到达。再试试点 4 和 7:LCA = 2,dist = 2+3−2 = 3(奇数)。点 7 和 8:LCA = 5,dist = 3+3−4 = 2(偶数)→ 偶数步可以到!

朴素 LCA 代码(一步一步往上爬)

// 前提:depth[] 和 fa[](爸爸)已经算好了
int lca(int u, int v) {
    while (depth[u] > depth[v]) u = fa[u];   // 深的先往上爬到同一层
    while (depth[v] > depth[u]) v = fa[v];
    while (u != v) {                          // 再一起往上爬
        u = fa[u];
        v = fa[v];
    }
    return u;                                 // 相遇点就是 LCA
}

// 距离公式
int dist = depth[u] + depth[v] - 2 * depth[lca(u, v)];
⚠️ 范围提醒:朴素 LCA 每次 O(深度),题目要求 n ≤ 2×10⁵ 且只问一个点时没问题;如果问很多组距离,就要用七级的倍增法了(本课不展开)。

6综合小测验

1️⃣ 一棵有 7 个节点的树,一共有几条边?

✔️ 6 条!n 个点的树一定有 n−1 条边。

2️⃣ 树上两个不同点之间,有几条不重复走点的路径?

✔️ 恰好 1 条!这是树的灵魂性质,距离才有唯一答案。

3️⃣ 判断题:任何树都可以用两种颜色染好,让相邻节点不同色。

✔️ 对!树一定是二部图:按深度奇偶染就行,相邻深度必差 1、奇偶必相反。

4️⃣ 本课演示树中(以 1 为根),depth[5]=2,depth[9]=3。节点 5 和 9 的距离是?

✔️ 5!路径 5-2-1-3-6-9 共 5 条边。LCA(5,9)=1,公式:2+3−2×0=5。

5️⃣ 两个节点的深度同为偶数,它们的距离是?

✔️ 偶数!dist = depth[u]+depth[v]−2×depth[LCA],两个偶数加起来是偶数,再减偶数还是偶数 → 它们可以偶数步互相到达!

6️⃣ 树去掉任意一条边,会发生什么?

✔️ 分成两半!树没有多余的边,每条边都是「命根子」。反之,给树随便加一条边,一定会出现环。

7总结 & 下一步

知识点一句话用在哪
树的定义连通 + 无环 = n 点 n−1 边读题识别树
邻接表vector<int> g[N],双向存边建树
DFS 前序先自己再孩子,递归求 depth、fa、子树大小
BFS 层序队列一层层走求 depth、最短路
深度与染色深度奇偶翻转染色,树是二部图判断两点能否偶数步到达
距离公式dist = depth[u]+depth[v]−2×depth[LCA]判断偶数步可达

如果这一课玩熟了,下一课可以挑战:树的直径(最远的两点有多远)、树的重心(把树切在哪最平衡),还有倍增 LCA(七级内容)。想学哪个,跟我说!