Prufer序列

Prufer序列是一种将带标号的 \(n\) 个节点树用一个唯一的长度为 \(n - 2\) 整数序列表示的方法。
每次选取编号最小的叶子,把他的baba加到Prufer序列里,然后把这个点删了。
(你可能问说有没有别的构造方式,但这是大家公认的构造方式,一般都这么构造)
这里就有一种用堆构造Prufer序列的方式。
即先把所有度为 \(1\) 的点加到堆里(按照编号为值)。
取堆顶(编号最小的),把他的baba加到Prufer序列里,再把当前点删了(对堆进行pop,并把他的baba的度减一),如果他的baba的度被减到 \(1\) 了,就把他的baba加到堆里。
复杂度为 \(\mathcal O(n\log n)\)。
还有一种 \(\mathcal O(n)\) 的方法。
看看代码(从OI-Wiki上复制的):
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;
}
因为只删了 \(n - 2\) 个点,最后肯定剩下两个点,其中一个是 \(n\)。
它的正确性显然与堆一致。
显然对于一棵树,这么构造的序列是唯一的。
显然,如果一个点在Prufer中出现了 \(k\) 次,那么他在树上的度数为 \(k + 1\)。
其实Prufer序列与带标号树构成双射。
所以这里还有一种Prufer序列 \(\to\) 带标号树的算法。
看看代码(也是从OI-Wiki上抄的):
vector<pair<int, int>> pruefer_decode(vector<int> const& code) {
int n = code.size() + 2;
vector<int> degree(n, 1);
for (int i : code) degree[i]++;
int ptr = 0;
while (degree[ptr] != 1) ptr++;
int leaf = ptr;
vector<pair<int, int>> edges;
for (int v : code) {
edges.emplace_back(leaf, v);
if (--degree[v] == 1 && v < ptr) {
leaf = v;
} else {
ptr++;
while (degree[ptr] != 1) ptr++;
leaf = ptr;
}
}
edges.emplace_back(leaf, n - 1);
return edges;
}

浙公网安备 33010602011771号