最小生成树
M S T MST MST是图 G G G中最小代价连通子图。
M S T MST MST具有以下的性质:
- 最小生成树不唯一,但是它们的边权和相同
- 最小生成树的边数等于顶点数减一(树的性质)
构造最小生成树的算法:
有两种经典的算法
p
r
i
m
prim
prim算法和
k
r
u
s
k
a
l
kruskal
kruskal算法。它们都基于贪心策略实现。
p
r
i
m
prim
prim是对点进行贪心,所以适用于稠密图。而
k
r
u
s
k
a
l
kruskal
kruskal是对边进行贪心,适用于稀疏图。以下是两种算法的代码实现:
-
p
r
i
m
prim
prim
p r i m prim prim与课本上 d i j k s t r a dijkstra dijkstra算法的实现几乎一样。唯一的区别在于更新 d i s dis dis数组的方式。 p r i m prim prim是更新加入MST的最小代价,而 d i j k s t r a dijkstra dijkstra是更新路径长度。
int prim(){
int res=0,cnt=0; //记录路径长度和边的数量
bool vis[maxn];
int dis[maxn];
dis[1] = 0;
for(int i=0;i<n;i++){
int mx = inf, index = -1;
for(int j=1;j<=n;j++){ //标记下一个加入MST的点
if(!vis[j]&&mx>dis[j])
mx = dis[j] , index = j;
}
if(index==-1) break; //如果不存在,跳出循环
for(int j=1;j<=n;j++){ //用标记过的点更新其他没有加入MST的点
if(!vis[j]){ //邻接矩阵存图
dis[j] = min(g[index][j],dis[j]); //更新加入MST的最小代价
}
}
vis[index] = true;
res += dis[index];
cnt++;
}
if(cnt!=n-1){ //如果边数不是n-1,则不能构成MST
cout<<"不能构成MST"<<endl;
return -1;
}
else
return res;
}
时间复杂度分析:一共要贪心 ∣ V ∣ − 1 |V|-1 ∣V∣−1个点所以需要 ∣ V ∣ − 1 |V|-1 ∣V∣−1次循环。在循环内部,需要对 d i s dis dis数组进行遍历 O ( ∣ V ∣ ) O(|V|) O(∣V∣),标记最小没有加入集合的点。所以时间复杂度为 O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2)。
- k r u s k a l kruskal kruskal
struct Edge {
int u,v,w;
};
Edge e[200005]; //存边
int fa[5005],n,m,ans,eu,ev,cnt;
bool cmp(const Edge& a, const Edge& b){
return a.w<b.w;
}
int find(int x){ //并查集
while(x!=fa[x]) x=fa[x]=fa[fa[x]];
return x;
}
void kruskal(){
sort(e,e+m,cmp); //对边排序,便于后面贪心
for(int i=0;i<m;i++) {
eu = find(e[i].u),ev = find(e[i].v);
if(eu==ev) continue;
//如果都在一个集合里面不需要合并(不一定是在MST中,有可能存在多个集合还没有合并)
ans+=e[i].w;
fa[eu]=ev; //合并集合
if(++cnt==n-1) break;
}
}
时间复杂度分析:首先要对 ∣ E ∣ |E| ∣E∣条边排序 O ( ∣ E ∣ log ∣ E ∣ ) O(|E|\log{|E|}) O(∣E∣log∣E∣)。然后需要贪心选择 ∣ E ∣ − 1 |E|-1 ∣E∣−1条边,其中带路径在压缩的并查集时间复杂度近似为 O ( 1 ) O(1) O(1)。因为两种操作时并行的。所以选择较大的。所以时间复杂度为 O ( ∣ E ∣ log ( ∣ E ∣ ) ) O(|E|\log(|E|)) O(∣E∣log(∣E∣))
最小瓶颈路
def
给定一个加权无向图两个节点 u u u和 v v v,求 u u u到 v v v的一条路径,使得路径上边的最大权值最小。
首先,任意两个节点的最小瓶颈路一定在最小生成树上。所以我们可以先求出最小生成树。然后从 u u u点DFS到 v v v点,这样实现的时间复杂度是 O ( log n ) O(\log{n}) O(logn)。显然会TLE。
所以我们考虑在查询之前进行预处理,将所有节点对的最长边保存在一个
m
a
x
c
o
s
t
maxcost
maxcost数组中,然后查询只要KaTeX parse error: Expected 'EOF', got '}' at position 4: O(1}̲)。递推公式如下:
m
a
x
c
o
s
t
[
j
]
[
u
]
=
m
a
x
(
m
a
x
c
o
s
t
[
j
]
[
f
a
[
u
]
]
,
m
a
x
c
o
s
t
[
j
]
[
u
]
)
maxcost[j][u] = max(maxcost[j][fa[u]],maxcost[j][u])
maxcost[j][u]=max(maxcost[j][fa[u]],maxcost[j][u])
更新
j
j
j到
u
u
u的最小瓶颈路是
j
j
j到
f
a
[
u
]
fa[u]
fa[u]或
j
j
j到
u
u
u最小瓶颈路的最大值。
实现方法
基于prime算法
在求解最小生成树的过程中将有根树建立起来,同时求出所有节点的
m
a
x
c
o
s
t
maxcost
maxcost
int prime(){
int res = 0;
memset(maxcost,0,sizeof(maxcost));
for(int i=1;i<=n;i++){
vis[i]=0 , d[i] = INF , pre[i] = i;
}
d[s] = 0; //预先选中s
for(int i=0;i<n;i++){
int mx = INF , index = -1;
for(int j=1;j<=n;j++){
if(!vis[j]&&d[j]<mx)
mx = d[index=j]; //选中d最小的点
}
if(index==-1) break;
for(int j=1;j<=n;j++) //j->index = j->fa->index
if(vis[j])
maxcost[index][j] = maxcost[j][index] =
max(maxcost[pre[index]][j],mx);
res += mx;
vis[index] = 1;
for(int j=1;j<=n;j++){ //用选中的index更新其他节点
if(!vis[j]&&g[index][j]<d[j]){
d[j] = g[index][j];
pre[j] = index;
}
}
}
return res;
}