常用的是两种基于贪心的算法。
类似于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算法可以看做是一个不停地往最小生成树里加边的过程。
算法流程如下:
求解有 k k k棵树的最小生成树森林,同样可以使用Kruskal算法。
与求解最小生成树的流程区别就是把终止条件改为选出 n − k n-k n−k条边。
目前不会
在原图上任取一个环,环上权值最大的边一定不出现在最小生成树中
把节点分为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数列是一种无根树的数列,由一棵 n n n个结点的树转化的Prufer数列长度为 n − 2 n-2 n−2。
对于一棵有 n n n个结点的无根树,可以通过迭代删点的方式求出Prufer数列。
流程如下:
设 { a n − 2 } \{a_{n-2}\} {an−2}为一棵树的Prufer数列, { G n } \{G_n\} {Gn}为点集,则将Prufer数列转换为无根树的流程为:
可以用于求解满足给定条件的生成树共有多少种。
目前不会。。。
一张有 n n n个结点的完全图,有 n n − 2 n^{n-2} nn−2棵生成树。
证明
由于每一棵生成树对应一个prufer数列,而prufer数列有 n n − 2 n^{n-2} nn−2个,所以共有 n n − 2 n^{n-2} nn−2棵生成树。