Prufer 序列
一棵有标号无根树可以与它的 Prufer 序列唯一对应,通过生成 / / / 还原方法可以了解为什么一一对应。
树 - Prufer 序列
将编号最小的叶子节点删除,然后将它的父节点的编号记录在序列尾部,重复执行直到树只有一个节点,如此得到的序列即为它的 Prufer 序列。
实现
显然的实现方法是使用一个优先队列,时间复杂度 O ( n log n ) O(n\log n) O(nlogn)。
但还有线性做法:用一个指针 j j j 表示最小的叶子节点,删掉 j j j 节点。如果删掉 j j j 节点后,它的父亲 k k k 变为了叶子且 k < j k < j k<j,则删掉 k k k,如果 k k k 的父亲编号仍然比 j j j 小,重复上述过程。每次删完节点记录对应父节点的编号即可。 j j j 的移动是单调的,因此时间复杂度 O ( n ) O(n) O(n)。
代码(Deg
儿子个数数组,Pru
Prufer 序列,Par
父亲数组):
for (int i = 1, j = 1; i <= N - 2; i++, j++) {
// j 是最小叶节点的编号, i 是当前 Prufer 序列末尾位置
while (Deg[j]) j++;
Pru[i] = Par[j];
while (i < N - 2 && --Deg[Pru[i]] == 0 && Pru[i] < j)
Pru[i + 1] = Par[Pru[i]], i++; // Pru[i] 即为上文中的 k
}
Prufer 序列 - 树
即 树 - Prufer 序列 的逆变换。
实现
一个数在 Prufer 序列中出现的次数就是这个编号节点在树上的儿子个数,用 j j j 表示未出现过的最小编号节点,当前 Prufer 序列首元素即为点 j j j 的父亲,然后删掉 Prufer 序列首元素且其儿子个数减一,如果该元素变为了叶节点,用上文类似的方法循环处理即可。
代码(节点个数 N
为根):
Pru[N - 1] = N; // 最后一个节点删掉后,根 N 视为它的父亲
for (int i = 1, j = 1; i < N; i++, j++) { // i 是当前 Prufer 序列首元素下标
while (Deg[j]) j++;
Par[j] = Pru[i];
while (i < N && --Deg[Pru[i]] == 0 && Pru[i] < j)
Par[Pru[i]] = Pru[i + 1], i++;
}