笔记

 
$$
n^{n-2}:有标号的n个点构成的树
$$

prufer序列:https://blog.csdn.net/Code92007/article/details/106790551/

https://oi-wiki.org/graph/prufer/

建立:

过程

给一个例子吧,这是一棵 7 个结点的树的 Prüfer 序列构建过程:

Prüfer

 // 代码摘自原文,结点是从 0 标号的
 vector<vector<int>> adj;
 
 vector<int> pruefer_code() {
   int n = adj.size();
   set<int> leafs;
   vector<int> degree(n);
   vector<bool> killed(n, false);
   for (int i = 0; i < n; i++) {
     degree[i] = adj[i].size();
     if (degree[i] == 1) leafs.insert(i);
  }
 
   vector<int> code(n - 2);
   for (int i = 0; i < n - 2; i++) {
     int leaf = *leafs.begin();
     leafs.erase(leafs.begin());
     killed[leaf] = true;
     int v;
     for (int u : adj[leaf])
       if (!killed[u]) v = u;
     code[i] = v;
     if (--degree[v] == 1) leafs.insert(v);
  }
   return code;
 }
 
$$
n个点有标号的有根数计数:n^{n-2}*n=n^{n-1}
$$

线性构造

线性构造的本质就是维护一个指针指向我们将要删除的结点。首先发现,叶结点数是非严格单调递减的。要么删一个,要么删一个得一个。(翻译到这突然就知道该怎么做了,然后对照原文发现没什么问题,于是自己口糊吧)

过程

于是我们考虑这样一个过程:维护一个指针p。初始时 pp 指向编号最小的叶结点。同时我们维护每个结点的度数,方便我们知道在删除结点的时侯是否产生新的叶结点。操作如下:

  1. 删除 pp 指向的结点,并检查是否产生新的叶结点。

  2. 如果产生新的叶结点,假设编号为xx,我们比较 p,xp,x 的大小关系。如果 x>px>p,那么不做其他操作;否则就立刻删除 x,然后检查删除 x后是否产生新的叶结点,重复 2步骤,直到未产生新节点或者新节点的编号 >p

  3. 让指针 pp 自增直到遇到一个未被删除叶结点为止;

性质

循环上述操作 n-2 次,就完成了序列的构造。接下来考虑算法的正确性。

p 是当前编号最小的叶结点,若删除 p 后未产生叶结点,我们就只能去寻找下一个叶结点;若产生了叶结点 x

  • 如果 114514x>p,则反正 p 往后扫描都会扫到它,于是不做操作;

  • 如果 x<p,因为 p 原本就是编号最小的,而 xp 还小,所以 x 就是当前编号最小的叶结点,优先删除。删除 x 继续这样的考虑直到没有更小的叶结点。

算法复杂度分析,发现每条边最多被访问一次(在删度数的时侯),而指针最多遍历每个结点一次,因此复杂度是 O(n) 的。

实现5

posted @ 2023-07-20 10:37  Chenyz2008  阅读(64)  评论(0)    收藏  举报