之前写过Kruskal算法实现最小生成树,这次补上Prim算法
上次没有介绍关于最小生成树,这次一起补上吧
连通并且没有回路的无向图被称作是树
而给定一个无向图,就可以基于无向图构造一棵树,这就是图的生成树,图的生成树可能不止一棵
当这个无向图为连通的赋权图时,在无向图的所有生成树中,必然存在一个边的权值和最小的生成树,被称作最小生成树
最小生成树的性质也再看一下
- 最小生成树不能形成回路
- 边的数量等于点的数量减一,即 m = n - 1
Kruskal算法和Prim算法其实都是基于贪心,不过Kruskal算法需要先对边的权值排序,而Prim算法是每次都选取当前权值最小的边
Prim算法:
1.定义 i = 0,U = 空集,从连通图 G = {V,E} 中的某一顶点u出发,选择与它关联的具有最小权值的边 (u,v),将其顶点加入到生成树的顶点集合U中
2.以后每一步从一个顶点在U中,而另一个顶点不在U中的各条边中选择权值最小的边(u,v)把它的顶点加入到集合U中, i++
3.如果 i = n - 1,算法结束,否则就继续进行第二步,选取其他的边
根据前面的存图方法链式前向星就可以写出Prim算法的代码
我这里用了优先队列来优化
最小生成树模板
ll ans;
int n,m,num,cnt;
int vis[maxn],dis[maxn];
int head[maxn];
typedef pair <int,int> pii;
priority_queue<pii , vector <pii> , greater <pii> > q;
// 优先队列优化 记录边权和下一个结点
struct node // 链式前向星
{
int to;
int val;
int next;
}E[maxn*2];
void add(int u,int v,int w)
{
cnt++;
E[cnt].to = v;
E[cnt].val = w;
E[cnt].next = head[u];
head[u] = cnt;
}
void Prim()
{
q.push(make_pair(0,1));
while(!q.empty() && num < n)
{
int d = q.top().first;
int u = q.top().second;
q.pop();
if(vis[u]) continue;
ans += d;
num++; // 找到一条边
vis[u] = 1; // 标记一下,表示我这条边已经用过
for(int i=head[u];i;i=E[i].next)
if(E[i].val < dis[E[i].to]){ // 如果当前边权小,更新
dis[E[i].to] = E[i].val;
q.push(make_pair(E[i].val,E[i].to)); // 把新的边权和结点加入队列
}
}
}
int main(){
std::ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
for(int i=1;i<maxn;i++) dis[i] = 0x3f3f3f3f; // 初始化距离最大
cin >> n >> m;
for(int i=0;i<m;i++)
{
int u,v,w;
cin >> u >> v >> w;
add(u,v,w);
add(v,u,w); //无向图
}
Prim();
n == num ? cout << ans << endl : cout << "orz" << endl;
return 0;
}
当然还有一个不加优先队列优化的版本,我也放在这里了
int n,m,now,num,cnt,ans;
int vis[maxn],dis[maxn],head[maxn];
// 标记数组 最短距离 head数组
struct Edge{ // 链式前向星
int to;
int val;
int next;
}E[maxn];
void add(int u,int v,int w) // 存图
{
++cnt;
E[cnt].next = head[u];
E[cnt].to = v;
head[u] = cnt;
E[cnt].val = w;
}
int Prim()
{
now = 1;
for(int i=2;i<=n;i++) dis[i] = 0x3f3f3f3f; // 初始化距离为最大
for(int i=head[1];i!=0;i=E[i].next) // 从1顶点开始
dis[E[i].to] = min(E[i].val,dis[E[i].to]); // 找边权最小点
while(num < n-1){
int minn = 0x3f3f3f3f;
vis[now] = 1; // 第一个点已经确定,标记一下
for(int i=1;i<=n;i++){
if(minn > dis[i] && !vis[i]){
minn = dis[i];
now = i;
}
}
ans += minn;
num++; // 边加进去
for(int i=head[now];i!=0;i=E[i].next){ // 当前起点为now
if(dis[E[i].to]>E[i].val && !vis[E[i].to])
dis[E[i].to] = E[i].val;
}
}
return ans;
}
int main(){
io >> n >> m;
for(int i=0;i<m;i++){
int u,v,w;
io >> u >> v >> w;
add(u,v,w);
add(v,u,w); // 无向图
}
printf("%d\n",Prim());
return 0;
}