最小生成树
有 n n n 个顶点,用 n − 1 n-1 n−1 条边把一个连通图连接起来,并且使得权值的和最小的树就是最小生成树。
p r i m prim prim 算法采用的是一种贪心
的策略。每次将离连通部分的最近的点和点对应的边加入的连通部分,连通部分逐渐扩大,最后将整个图连通起来,并且边长之和最小。
与 D i j k s t r a Dijkstra Dijkstra类似, P r i m Prim Prim 算法也可以用堆优化
,优先队列代替堆
,优化的 P r i m Prim Prim 算法时间复杂度 O ( m l o g n ) O(mlogn) O(mlogn) ,适用于稀疏图。但是稀疏图的时候求最小生成树,Kruskal 算法更加实用
。
P r i m 算 法 求 最 小 生 成 树 Prim算法求最小生成树 Prim算法求最小生成树
P
r
i
m
Prim
Prim算法和
d
i
j
k
s
t
r
a
dijkstra
dijkstra 算法非常相似,Dijkstra算法是更新到起始点的距离,Prim是更新到集合S的距离
d
i
j
k
s
t
r
a
:
dijkstra:
dijkstra:先初始化距离,每次找不在集合中,距离最小的点,来更新它的邻点到
1
1
1 号点的距离。这个集合是指已经确定了最短距离的点的集合
P
r
i
m
:
Prim:
Prim:
1、先把所有距离初始化为正无穷
2、迭代
n
n
n 次:
f
o
r
(
i
=
1
;
i
<
=
n
;
+
+
i
)
for(i=1;i<=n;++i)
for(i=1;i<=n;++i)
2.1、每次也是找集合外距离集合最短的点
t
t
t,这里的集合是指已经在连通块中的点构成的集合
2.2、用
t
t
t 来更新其他点到集合的距离,
d
i
j
k
s
t
r
a
dijkstra
dijkstra是用
t
t
t 更新其他点到起点的距离
2.3、把
t
t
t加到集合中去,即
s
t
[
t
]
=
t
r
u
e
st[t]=true
st[t]=true
模板题链接:AcWing 858. Prim算法求最小生成树
一些注意事项:
①:要把
a
r
r
arr
arr数组初始化为正无穷,存在重边
①:初始化dist数组的时候不用把设置
d
i
s
t
[
1
]
=
0
dist[1]=0
dist[1]=0,此时的
d
i
s
t
dist
dist 数组表示到集合的距离
②:
i
f
(
i
if(i
if(i &&
d
i
s
t
[
t
]
=
=
I
N
F
)
dist[t] == INF)
dist[t]==INF),把
i
i
i 放进去是因为第一个点的时候还没有生成树集合产生
③:
i
f
(
i
)
r
e
s
+
=
d
i
s
t
[
t
]
if(i) res += dist[t]
if(i)res+=dist[t] ,此时的
r
e
s
res
res 表示第i次生成树中边权和的累加,当
i
=
0
i=0
i=0 的时候只有一个点,不存在距离这一说法。这里要把这一句放在更新距离之前来做,是因为假如存在负自环,假如之前
d
i
s
t
[
t
]
>
0
dist[t]>0
dist[t]>0 ,在更新距离中,当
j
=
t
j=t
j=t 时,他会更新自己的
d
i
s
t
[
t
]
dist[t]
dist[t]为负数 ,那么就不对了,因为我们不考虑负自环
AC核心代码:
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
int arr[510][510], dis[510];
bool flag[510];
int n, m, u, v, w;
int prim()
{
int res = 0;
memset(dis, 0x3f, sizeof(dis));
for(int i = 0; i < n; ++i)
{
int t = -1;
for(int j = 1; j <= n; ++j)
if(!flag[j] && (t == -1 || dis[t] > dis[j]))//这里的dis代表的是这个点到集合的距离
t = j;
if(i && dis[t] == INF)//首先排除第一个点,并且得出的t这个点距离集合的距离为INF说明他不会在集合里面,说明这个图不存在最小生成树
return INF;
if(i)//第一个点肯定不能加进去,因为这里我们大循环要循环n次,我们是从0开始的
res += dis[t];//这里要把距离更新写在前面,避免负自环的影响
for(int j = 1; j <= n; ++j)//更新与t这个点相连的点的距离,因为此时t已经确定可以加入集合了
dis[j] = min(dis[j], arr[t][j]);
flag[t] = true;
}
return res;
}
int main()
{
memset(arr, 0x3f, sizeof(arr));
scanf("%d%d", &n, &m);
while(m--)
{
scanf("%d%d%d", &u, &v, &w);
arr[v][u] = arr[u][v] = min(arr[u][v], w);
}
int t = prim();
if(t == INF)
printf("impossible");
else
printf("%d", t);
return 0;
}
K r u s k a l Kruskal Kruskal求最小生成树 ( O ( m l o g n ) ) (O(mlogn)) (O(mlogn))—一般是稀疏图
步骤如下:
1、将所有边按权重从小到大排序
(
O
(
m
l
o
g
m
)
)
(O(mlogm))
(O(mlogm))
2.1 枚举每条边
a
→
b
a→b
a→b,权重
c
c
c
2.2 如果
a
,
b
a,b
a,b 不连通,那么就将这条边假如到集合中 (并查集来做)
3.最后判断加的边数是否等于
n
−
1
n-1
n−1
模板题:AcWing 859. Kruskal算法求最小生成树
AC代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+10;
int n,m,u,v,w,p[MAXN];
struct Edge
{
int u,v,w;
}edge[MAXN];
bool cmp(Edge a,Edge b)
{
return a.w<b.w;
}
int find(int x)
{
if(p[x]!=x)
p[x]=find(p[x]);
return p[x];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)//初始化
p[i]=i;
for(int i=1;i<=m;++i)
{
scanf("%d%d%d",&u,&v,&w);
edge[i]={u,v,w};
}
sort(edge+1,edge+m+1,cmp);//根据边的权重来排序
int res=0,cnt=0;
for(int i=1;i<=m;++i)
{
int a=edge[i].u,b=edge[i].v,w=edge[i].w;
a=find(a),b=find(b);
if(a!=b)
{
++cnt;//边数+1
p[a]=b;//合并
res+=w;//加上权重
}
}
if(cnt<n-1)//如果边数小于n-1,说明n个点没有全部连通
printf("impossible");
else
printf("%d",res);
return 0;
}