Prüfer 序列
介绍
Prüfer 序列是一种将有标号无根树表示为序列的方法。
其中不考虑只有一个节点的树。
Prüfer 序列可以用 \(n-2\) 个数唯一表示一个有标号的无根树,当然一个有标号无根树也唯一对应一个Prüfer 序列。
Heinz Prüfer 于 1918 年发明这个序列来证明 凯莱公式.
对树建立 Prüfer序列
Prüfer 序列的构造方式十分简单。
从所有叶节点中选一个最小的,将其删除,并将其父亲加入序列,这样直到树只剩下两个节点。
下面举个例子:
来自OI-wiki
最直接,最简单的方式就是实用小根堆priority_queue在这里不做赘述,下面要说一种线性构造方式。
我们需要维护一个指针 \(p\),以及每个节点的父亲 \(fa\)、度 \(deg\)。
-
初始时 \(p\) 指向 \(1\) 然后开始自增。
-
当第一次到达一个叶节点时,当前叶节点一定时最小的,将其 \(fa\) 加入 Prüfer 序列,\(fa\) 的度减一,并检查其父亲是否成为新的叶节点,若不是叶节点,则不做任何操作,若是叶节点,检查 \(fa\) 的大小是否小于 \(p\)。
- 若 \(fa\) 小于 \(p\) 将 \(fa\) 执行上述操作。
- 若 \(fa\) 大于 \(p\) 不做任何操作, \(p\) 继续自增寻找下一个叶节点。
-
直到还剩下两个节点Prüfer 序列构造完毕。
具体细节请看代码:
void encode(){
for (int i = 1; i < n; i++) ++d[fa[i]],++d[i];
int ptr=-1;
for (int i = 1; i <= n; i++){
if (d[i]==1&&ptr==-1) ptr=i; //指向第一个度数为1的节点
}
int leaf=ptr;//leaf也是一个指针
for (int i=1;i<=n-2;i++){
int j=fa[leaf];
p[i]=j;//将leaf的fa加入 Prüfer 序列
if(--d[j]==1&&j<ptr){
leaf=j;//若fa度数为1且小于p,则将leaf指向fa
}else{
++ptr;
while(d[ptr]!=1)++ptr;//自增寻找下一个度数为1的节点
leaf=ptr;
}
}
}
对 Prüfer 序列重建树
类似于 Prüfer 序列的建立过程。
Prüfer 序列有以下性质:
- 每个节点的度数为该节点在Prüfer序列中出现的次数加一。
- 构建Prüfer序列所剩余的节点中,一定有一个是最大的节点。
于是对于性质1,我们可以还原出每个节点的度数,对于性质2,我们可以得知最大节点所连的节点。
具体重构过程如下:
- 还原每个节点的度数。
- 维护指针 \(p\) 指向第一个度数为 \(1\) 的节点, \(i\) 指向Prüfer序列中第一个数。
- 将 \(p\) 的 \(fa\) 设为 \(Prüfer_i\),将 \(Prüfer_i\) 对应的节点度数减一并检查 \(Prüfer_i\) 所对应的节点的度数是否为 \(1\),若不是则不做操作,若是继续检查 \(Prüfer_i\) 是否小于 \(p\) 若是则将 \(p\) 指向 \(Prüfer_i\),后 \(p\) 自增寻找下一个度数为 \(1\) 的节点。
- 最后将 \(n\) 的 \(fa\) 设为 \(p\)。
具体细节请看代码:
void decode(){
for(int i=1;i<=n;i++) d[i]=1;
for(int i=1;i<=n-2;i++) ++d[p[i]]; //还原每个节点的度数
int ptr=1;
while(d[ptr]!=1) ++ptr;//指向第一个度数为1的节点
int leaf=ptr;
for (int i=1;i<=n-2;i++){
fa[leaf]=p[i];//将p的fa设为Prüfer[i]
if(--d[p[i]]==1&&p[i]<ptr){
leaf=p[i];//若Prüfer[i]的度数为1且小于p,将leaf指向Prüfer[i]
}else{
++ptr;
while(d[ptr]!=1) ++ptr;//自增寻找下一个度数为1的节点
leaf=ptr;
}
}
fa[leaf] = n;//将n的fa设为p或将p的fa设成n
}


浙公网安备 33010602011771号