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 序列有以下性质:

  1. 每个节点的度数为该节点在Prüfer序列中出现的次数加一。
  2. 构建Prüfer序列所剩余的节点中,一定有一个是最大的节点。

于是对于性质1,我们可以还原出每个节点的度数,对于性质2,我们可以得知最大节点所连的节点。
具体重构过程如下:

  1. 还原每个节点的度数。
  2. 维护指针 \(p\) 指向第一个度数为 \(1\) 的节点, \(i\) 指向Prüfer序列中第一个数。
  3. \(p\)\(fa\) 设为 \(Prüfer_i\),将 \(Prüfer_i\) 对应的节点度数减一并检查 \(Prüfer_i\) 所对应的节点的度数是否为 \(1\),若不是则不做操作,若是继续检查 \(Prüfer_i\) 是否小于 \(p\) 若是则将 \(p\) 指向 \(Prüfer_i\),后 \(p\) 自增寻找下一个度数为 \(1\) 的节点。
  4. 最后将 \(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
}
posted @ 2026-07-25 15:42  LZYXT  阅读(14)  评论(0)    收藏  举报