[学习笔记] Prüfer 序列
定义
Prüfer 序列是带标号无根树和序列的双射
Prüfer 序列可以用一个长度为
其常用在树的计数问题中
构造
树->序列
Prüfer 序列构造的根本在于每次选取编号最小的叶节点,将其删除并加入序列,直到剩下
方法一
用小根堆维护编号最小的度数为
时间复杂度
方法二
发现带个
本质是用指针来维护我们将要删除的节点
记
首先,用指针
其次,将其父亲节点
然后,再用一个指针
若
处理
否则,让
容易发现,每个节点最多只会被处理一次,因此复杂度为
以下是代码参考
vector<vector<int>> adj;
vector<int> parent;
void dfs(int v) {
for (int u : adj[v]) {
if (u != parent[v]) parent[u] = v, dfs(u);
}
}
vector<int> pruefer_code() {
int n = adj.size();
parent.resize(n), parent[n - 1] = -1;
dfs(n - 1);
int ptr = -1;
vector<int> degree(n);
for (int i = 0; i < n; i++) {
degree[i] = adj[i].size();
if (degree[i] == 1 && ptr == -1) ptr = i;
}
vector<int> code(n - 2);
int leaf = ptr;
for (int i = 0; i < n - 2; i++) {
int next = parent[leaf];
code[i] = next;
if (--degree[next] == 1 && next < ptr) {
leaf = next;
} else {
ptr++;
while (degree[ptr] != 1) ptr++;
leaf = ptr;
}
}
return code;
}
序列->树
由 Prüfer 序列可知每个点度数,考虑维护当前编号最小的叶子
我们需要重复进行以下操作,直至点集中只剩下两个点:
1.取出点集中最小的不在 Prüfer 序列中的元素
2.取出 Prüfer 序列最前面的元素
3.将
注意:上述的取出相当于删除
最后,点集中会剩下两个点,将它们连边即可
显然我们只会连
性质
1.编号最大的点一定不会被删除
证明:
正确性显然,因为一棵节点数量大于等于
因此即使编号最大的点是叶子节点也不会被删除
2.每个点在 Prüfer 序列中的出现次数为其度数减
证明:
对于除根节点外的所有点,他们都会被儿子加入序列,故它们出现次数为度数减
对于根节点,其最终不会被被删去,即其会成为剩下来的点
故其会被除了剩下来的另外一个点以外的儿子加入序列,其出现次数为度数减
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现