地址:
https://www.acwing.com/problem/content/860/
很好的题解有输出路径的方式:
https://www.acwing.com/solution/content/38312/
描述:
思想:
流程:
S:当前已经在联通块中的所有点的集合
1. dist[i] = inf
2. for n 次
t<-S外离S最近的点
利用t更新S外点到S的距离
st[t] = true
n次迭代之后所有点都已加入到S中
区别:
prim算法其实和Dijkstra算法很类似,最核心的区别在于:
Dijkstra算法是更新到起始点的距离,Prim是更新到集合S的距离
还有一个很重要的区别就是dist[]数组含义不一样
dijkstra算法:
if(dist[j]>dist[t]+g[t][j]) dist[j]=dist[t]+g[t][j];
prim算法:
if(dist[j]>g[t][j]) dist[j]=g[t][j];
至于为什么这是因为:dist[N]数组的含义不一样
在dijkstra算法中dist[]存储的是节点到起点的距离
而在prim算法里dist[]存储的是节点到已经构建好的生成树集合的最短距离
令我困惑的点:
for(int j=1;j<n;j++){
if(dist[j]>g[t][j]) dist[j]=g[t][j];}
这句话的涵义是为了更新生成树外的点到生成树的距离
按道理来说dist[j]应该与(j节点到生成树中的每一个节点的距离)都进行一次对比为什么这里只是让dist[j]和g[t][j](t节点到j节点的距离)做比较呢?不会产生少比较然后导致更新不准确的情况吗?
其实这里我们完全不用担心,在自己过一遍流程后就会发现
代码:
prim算法
#include <iostream>
#include <cstring>
using namespace std;
const int N=510;
#define INF 0x3f3f3f3f
int n,m;
int g[N][N];
//dist[]存放某一节点到已经构建好的最小生成树集合的最短距离
int dist[N];
bool stu[N];
int prim(){
dist[1]=0;
int res=0;//是最小生成树的树边权重之和
for(int i=0;i<n;i++){
int t=-1;
//找到与集合最近的点
for(int j=1;j<=n;j++){
if(!stu[j]&&(t==-1||dist[t]>dist[j])){
t=j;
}//if
}//for(j)
if(dist[t]==INF) return INF;
else res+=dist[t];
stu[t]=true;
//更新生成树外的点到生成树的距离
for(int j=1;j<=n;j++){
//dist[j]=min(dist[j],g[t][j]);
//dist[j]存储j节点到已经构建的最小生成树集合的最小距离
//g[t][j]存储的是t节点到j节点的联通边的距离
if(dist[j]>g[t][j]) dist[j]=g[t][j];
}
}//for(i)
return res;
}
int main(){
cin>>n>>m;
memset(g,0x3f,sizeof g);
memset(dist,0x3f,sizeof dist);
for(int i=0;i<m;i++){
int a,b,c;
cin>>a>>b>>c;
//have重边,且是无向图
g[a][b]=g[b][a]=min(g[a][b],c);
}
int t=prim();
if(t==INF) cout<<"impossible";
else cout<<t;
return 0;
}
与之对比的Dijkstra算法
#include <iostream>
#include <cstring>
using namespace std;
const int N=510;
#define INF 0x3f3f3f3f
//用来存放节点间距离
int g[N][N];
//确定好与起点最短距离的节点
int stu[N];
//节点与起点之间距离
int dist[N];
int n,m;
int Dijkstra(){
//起点与起点的距离为0
dist[1]=0;
//迭代n次,每次可以确定一个点到起点的最短路
for(int i=0;i<n;i++){
//t的作用判断有没有更新过
int t=-1;
//从1~n号节点中找,不在s集合,并且
//没有更新过(t==-1),则进行更新, 或者发现更短的路径(dist[t]>dist[j]),则进行更新
for(int j=1;j<=n;j++){
if(!stu[j]&&(t==-1||dist[t]>dist[j])) {
t=j;
}//if
}//for(j)
stu[t]=true;
//找到了距离最小的点t,并用最小的点t去更新1~n号节点到起点的距离
//这里可能有同学要问j如果从1开始的话 会不会影响之前已经确定的点的最小距离
//但其实是不会 因为按照我们的Dijkstra算法的操作顺序
//先确定最短距离的点的距离已经比后确定的要小 所以不会影响
//当然你也可以在循环判断条件里加上if(!st[i])
//这里j从1开始只是为了代码的简洁
for(int j=1;j<=n;j++){
dist[j]=min(dist[j],dist[t]+g[t][j]);
}//for(j)
}//for(i)
//说明没有出路
if(dist[n]==0x3f3f3f3f) return -1;
else return dist[n];
}
int main(){
cin>>n>>m;
//初始化距离
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j) g[i][j]=0;
else g[i][j]=INF;
}
}
//初始化dist[N]为无穷大
memset(dist,0x3f,sizeof dist);
for(int i=0;i<m;i++){
//m条边,但有重边,所以当节点间有重边时,取边长最小的一条边
int a,b,c;
cin>>a>>b>>c;
//取边长最短的一条边
g[a][b]=min(g[a][b],c);
}
cout<<Dijkstra();
return 0;
}