Prim算法
一、算法学习前先要知道
1.最小生成树(必须了解)
对于无向图G(V,E),连接所有点V以及边集是E的子集的树称为G的生成树。而边的权值和最小的树即为G的最小生成树。
2.Dijkstra算法(建议了解)
Prim算法与Dijkstra算法在思路上非常相似,如果能理解Dijkstra算法的话对于Prim算法的理解是很有帮助的。
博客链接:Dijkstra算法
二、算法实现步骤
1.初始化
- dis[N]:表示每个点到已经确定的集合的最短距离,初始化为无限大。
- G[N][N]:邻接矩阵存图。
- ch[N]:用来判断一个点是否已经在集合中。
2.寻找
每次寻找未确定的点中距离集合最短的点把它加入集合并且更新其它未在集合中的点的距离。(如果没看懂就结合后文代码理解)
伪代码:
for(循环n次,每次确定1个点)
{
for(循环n次遍历所有点,找出dis最短的点){}
for(遍历所有与该点连接的边,更新与之连接的点的dis值){}
标记一下这个点在集合里面了
}
时间复杂度为O(n2)
三、算法的代码实现
例题链接:Acwing Prim求最小生成树
给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。
求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。
给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,E 表示图中边的集合,n=|V|,m=|E|。
由 V 中的全部 n 个顶点和 E 中 n−1 条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G 的最小生成树。
输入格式
第一行包含两个整数 n 和 m。
接下来 m 行,每行包含三个整数 u,v,w,表示点 u 和点 v 之间存在一条权值为 w 的边。
输出格式
共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。
数据范围
1≤n≤500,
1≤m≤1e5,
图中涉及边的边权的绝对值均不超过 10000。
输入样例:
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出样例:
6
1.朴素Prim算法实现
- 由于时间复杂度O(n2),适合处理密集图
//#pragma GCC optimize(2)
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<stack>
#include<set>
#include<bitset>
#include<ctime>
#include<cstring>
#include<list>
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 7;
int n, m;
int G[550][550]; //邻接矩阵存图
int dis[550]; //距离数组
bool ch[550]; //标记
int prim()
{
int ans=0; //初始化答案
for (int j = 0; j < n; j++) //n次循环
{
int t = -1; //初始化
int mi = INF;
for (int i = 1; i <= n; i++)
if (!ch[i] && (t == -1 || dis[i] < mi)) t = i, mi = dis[i];//每次要找到距离集合最短的点,当集合为空时随便选一个(即t=-1时)
if (j && dis[t] == INF) //如果最小值为无限大代表无答案
return INF;
if(j) //如果不是第一次就加上边
ans += dis[t];
ch[t] = 1; //改一下标记
for (int i = 1; i <= n; i++)//遍历所有与t连接的边
{
if (!ch[i]) //如果不在集合中就更新dis
dis[i] = min(dis[i], G[t][i]);
}
}
return ans; //返回答案
}
void solve()
{
mem(dis, 0x3f); //初始化距离
mem(G, 0x3f); //图的初始化
cin >> n >> m;
for (int i = 1; i <= n; i++)G[i][i] = 0; //到自己的距离为0
for (int i = 0; i < m; i++)
{
int a, b, c;
cin >> a >> b >> c;
G[a][b] = G[b][a] = min(G[a][b], c); //重边选最小
}
int t = prim(); //接受函数的返回值
if (t >= INF)
cout << "impossible" << endl;
else
cout << t << endl;
}
int main()
{
std::ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
solve();
return 0;
}
2.Prim算法优先队列优化
- 注意:下面的代码是过不了题目的!原因是优先队列优化后时间复杂度为O(mlogn),当图为稀疏图时有很大的优势,但是在例题中是稠密图,m最差可以达到n2的数量级,时间上反而会更加慢。因此在考虑优先队列优化时一定要注意题目中数据大小。在这里只是介绍一下。
//#pragma GCC optimize(2)
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<stack>
#include<set>
#include<bitset>
#include<ctime>
#include<cstring>
#include<list>
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 7;
int n, m;
int h[550], e[550], ne[550], w[550];
int dis[550]; //距离数组
bool ch[550]; //标记
int id = 1;
void add(int a, int b,int c)
{
ne[id] = h[a], h[a] = id, e[id]=b, w[id++] = c;
}
int prim()
{
priority_queue<PII, vector<PII>, greater<PII>> q;
int cnt = 0;
int ans = 0;
dis[1] = 0;
q.push({ dis[1],1 });
while (!q.empty())
{
PII te = q.top();
q.pop();
int tid = te.second, tdis = te.first;
if (ch[tid])continue;
ch[tid] = 1;
cnt++;
ans += tdis;
for (int i = h[tid]; i != -1; i = ne[i])
{
int k = e[i];
int j = w[i];
if ( dis[k] > j)
{
dis[k] = j;
q.push({dis[k],k});
}
}
}
if (cnt == n)
return ans;
else
return INF;
}
void solve()
{
mem(dis, 0x3f); //初始化距离
mem(h, -1); //图的初始化
cin >> n >> m;
for (int i = 0; i < m; i++)
{
int a, b, c;
cin >> a >> b >> c;
if (a == b)continue;
add(a, b, c);
add(b, a, c);
}
int t = prim(); //接受函数的返回值
if (t >= INF)
cout << "impossible" << endl;
else
cout << t << endl;
}
int main()
{
std::ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
solve();
return 0;
}
作者:Avalon Demerzel,喜欢我的博客就点个赞吧,更多图论与数据结构知识点请见作者专栏《图论与数据结构》