最小生成树
常用的是两种基于贪心的算法。
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算法可以看做是一个不停地往最小生成树里加边的过程。
算法流程如下:
- 把图中所有边按权值从小到大排序,初始化每个结点所在的集合。
- 按顺序考虑每条边,若该边的两个端点 u u u、 v v v不在同一集合内,则将该边加入最小生成树中,并合并 u u u、 v v v所在集合。
- 重复以上过程,直到选出了
n
−
1
n-1
n−1条边。
时间复杂度 O ( m log m ) O(m\log m) O(mlogm)。
最小生成树森林
求解有
k
k
k棵树的最小生成树森林,同样可以使用Kruskal算法。
与求解最小生成树的流程区别就是把终止条件改为选出
n
−
k
n-k
n−k条边。
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 n−2。
无根树转Prufer数列
对于一棵有
n
n
n个结点的无根树,可以通过迭代删点的方式求出Prufer数列。
流程如下:
- 找到叶子结点中编号最小的结点,删除它和与它相连的边,并将这条边连接的另一个结点加入Prufer数列。
- 重复上述步骤,直到原图仅剩两个结点。
Prufer数列转无根树
设 { a n − 2 } \{a_{n-2}\} {an−2}为一棵树的Prufer数列, { G n } \{G_n\} {Gn}为点集,则将Prufer数列转换为无根树的流程为:
- 找出 { G n } \{G_n\} {Gn}中未在 { a n − 2 } \{a_{n-2}\} {an−2}中出现的最小的数,将其与 { a n − 2 } \{a_{n-2}\} {an−2}的首项连边,并将该点与 { a n − 2 } \{a_{n-2}\} {an−2}的首项删除。
- 重复上述步骤,直到生成一棵树。
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棵生成树。