图论——生成树

最小生成树

常用的是两种基于贪心的算法。

Prim

类似于Dijksrta求解单源最短路径的过程。
每次从为访问过的结点中选取出到树上的距离结点最小的结点,并用其更新其他结点到树上结点的距离,重复该过程直到所有结点都被访问(即生成了一棵树)。
通常取 1 1 1为初始结点

int dis[N], G[N][N];
bool vis[N];

int Prim() {
  int mst = 0;
  memset(dis, 0x3f, sizeof(dis)), dis[1] = 0;
  for (int k = 1; k <= n; ++k) {
    int u = 0;
    for (int i = 1; i <= n; ++i)
      if (!vis[i] && dis[u] > dis[i]) u = i;
    vis[u] = true;
    mst += dis[u];
    for (int v = 1; v <= n; ++v)
      dis[v] = min(dis[v], G[u][v]);
  }
  return mst;
}

时间复杂度 O ( n 2 ) O(n^2) O(n2)

优化

Dijkstra,每次查找 d i s dis dis最小的结点的过程可以用堆或线段树等数据结构优化,这里给出堆优化的代码。

int dis[N], G[N][N];
bool vis[N];
priority_queue<pair<int, int>,
  vector<pair<int, int> >, greater<pair<int, int> > > Q;

int Prim() {
  int mst = 0;
  memset(dis, 0x3f, sizeof(dis)), dis[1] = 0;
  Q.push(make_pair(0, 1));
  while (!Q.empty()) {
    int u = Q.top().second; Q.pop();
    if (vis[u]) continue;
    vis[u] = true;
    mst += dis[u];
    for (int v = 1; v <= n; ++v)
      if (dis[v] > G[u][v]) {
        dis[v] = G[u][v];
        Q.push(make_pair(dis[v], v));
      }
  }
  return mst;
}

时间复杂度 O ( m log ⁡ n ) O(m\log n) O(mlogn)

Kruskal

Kruskal算法可以看做是一个不停地往最小生成树里加边的过程。
算法流程如下:

  1. 把图中所有边按权值从小到大排序,初始化每个结点所在的集合。
  2. 按顺序考虑每条边,若该边的两个端点 u u u v v v不在同一集合内,则将该边加入最小生成树中,并合并 u u u v v v所在集合。
  3. 重复以上过程,直到选出了 n − 1 n-1 n1条边。
    时间复杂度 O ( m log ⁡ m ) O(m\log m) O(mlogm)

最小生成树森林

求解有 k k k棵树的最小生成树森林,同样可以使用Kruskal算法
与求解最小生成树的流程区别就是把终止条件改为选出 n − k n-k nk条边。

Kruskal重构树

目前不会

性质

回路性质

在原图上任取一个环,环上权值最大的边一定不出现在最小生成树中

切割性质

把节点分为S和T两部分,在所有连接S和T的边中,边权最小的那条一定出现在
最小生成树中

次小生成树

由切割性质可以得出结论,次小生成树与最小生成树仅有一条边的差距。
于是先求出最小生成树,枚举每一条非树边 ( u , v ) (u,v) (u,v),用 w ( u , v ) w(u,v) w(u,v)替换从 u u u v v v的最大边。这个过程可以用树上倍增优化。
总时间复杂度 O ( m log ⁡ n + m log ⁡ m + m α ( n ) ) O(m \log n + m \log m + m \alpha(n)) O(mlogn+mlogm+mα(n))

最短路径树

对于一张无负环图,选定一个源点,该源点到每个点的最短路径构成一棵树。

Prufer数列

Prufer数列是一种无根树的数列,由一棵 n n n个结点的树转化的Prufer数列长度为 n − 2 n-2 n2

无根树转Prufer数列

对于一棵有 n n n个结点的无根树,可以通过迭代删点的方式求出Prufer数列。
流程如下:

  1. 找到叶子结点中编号最小的结点,删除它和与它相连的边,并将这条边连接的另一个结点加入Prufer数列。
  2. 重复上述步骤,直到原图仅剩两个结点。

Prufer数列转无根树

{ a n − 2 } \{a_{n-2}\} {an2}为一棵树的Prufer数列, { G n } \{G_n\} {Gn}为点集,则将Prufer数列转换为无根树的流程为:

  1. 找出 { G n } \{G_n\} {Gn}中未在 { a n − 2 } \{a_{n-2}\} {an2}中出现的最小的数,将其与 { a n − 2 } \{a_{n-2}\} {an2}的首项连边,并将该点与 { a n − 2 } \{a_{n-2}\} {an2}的首项删除。
  2. 重复上述步骤,直到生成一棵树。

Prufer数列的应用

可以用于求解满足给定条件的生成树共有多少种。

生成树计数

矩阵树定理

目前不会。。。

完全图的生成树

一张有 n n n个结点的完全图,有 n n − 2 n^{n-2} nn2棵生成树。
证明
由于每一棵生成树对应一个prufer数列,而prufer数列有 n n − 2 n^{n-2} nn2个,所以共有 n n − 2 n^{n-2} nn2棵生成树。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值