Prufer 序列
Prufer 序列实际上是一种转化的产物,这种转化使得一棵有 个点的无根树可以在线性时间内与一个有 个元素,且序列中元素权值在 中的序列互相转化。
它与单纯的父节点组成的序列区别在于:对于每棵树,它的 Prufer 序列是唯一的,每一个 Prufer 序列也对应着唯一一棵树。换句话说,树与 Prufer 序列是双射关系。
Prufer 序列强大的地方就在这里。把一棵树转化为一个序列,而序列的计数难度明显小于树的计数。
由树转化成 Prüfer 序列
先给出转化的步骤:
- 寻找到当前最小的叶子节点 。
- 把 的父亲加入 Prüfer 序列。
- 从树上删去 。
- 重复前三个步骤,直到 Prüfer 序列有 个数。此时原树必定只剩下两个点。
一个显然的想法是使用小根堆,时间复杂度为 。但是在这里我们有线性做法:
- 找到当前最小叶子节点 。
- 把 的父亲 加入 Prüfer 序列,并将 的度数减少 。
- 如果 的度数变为 ,且 ,那么将 的父亲 加入 Prüfer 序列。因为此时 必定为最小叶子( 本来为最小叶子,但是 比 还小。在删去 的过程中没有除 以外的新叶子产生,所以 为最小叶子)。
- 重复第三步,每次都跟 比较。直到有一个不符合条件。此时增加 。
- 重复前四步,直到 Prüfer 序列有 个数,此时原树必定只剩下两个点。
for (int i = 1; i <= n; ++i) deg[fa[i]]++;
static int check[N], tot;
for (int i = 1; i <= n; ++i) if (deg[i] == 0) check[i] = 1;
for (int mn = 1; mn < n; ++mn) {
int u = mn;
while (check[u] == 1) {
p[++tot] = fa[u];
if (tot == n - 2) break;
check[u] = 0;deg[fa[u]]--;
if (deg[fa[u]] == 0) {
check[fa[u]] = 1;
if (mn > fa[u]) u = fa[u];
}
}
if (tot == n - 2) break;
}
Prüfer 序列的性质
- Prufer 序列中每个点出现的次数代表它有几个儿子,即度数 。
由 Prüfer 序列还原树
先给出还原步骤:
- 找到当前最小的,不在序列中的点 ,序列中的下标最小点 即为 的父亲。
- 删除序列中的第一个 。
- 重复以上步骤,最后会留下最大点和一个点 。在一般题目里我们不妨设最大点为无根树的假想根,那么最后的步骤是将 连向最大点。
类似于树转 Prüfer 序列,我们也有线性方法:
- 找到当前最小的,不在序列中的点 ,序列中下标最小的点 即为 的父亲。
- 删除序列中的第一个 ,如果 删除后不再在序列中出现,且 ,那么 的父亲即为序列中下标最小的点的父亲。
- 重复步骤二,每次与 比较,直到有一个不符合条件。此时增加 。
- 重复以上步骤,直到 Prüfer 序列为空。
for (int i = 1; i <= n - 2; ++i) deg[p[i]]++;
static int tot;
for (int mn = 1; mn < n; ++mn) {
int u = mn;
while (deg[u] == 0) {
fa[u] = p[++tot];
deg[u] = 1;
if (tot == n - 2) break;
deg[fa[u]]--;
if (deg[fa[u]] == 0) if (mn > fa[u]) u = fa[u];
}
if (tot == n - 2) break;
}
for (int i = 1; i < n; ++i) if (fa[i] == 0) fa[i] = n;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异