前言:
-
生成树:在一个图中将所有的结点连在一起建成一颗树(
最小连通图
) -
最小生成树:权值最小的生成树
因此,如果是生成树,那么原图必定是一个连通图
Prim算法
- 时间复杂度 O(|V|2),适和稠密图
基本过程
-
将图分成两个部分,在树里的部分和不在树里的部分
-
选择一条权值最小的边,且该边所连的两个点一个在树内,一个在树外
-
将选定的边加入树内,并将该边的权值记录
-
重复上述操作直到所有的点都被加入树中
实现
目标
读入一个含有 n 个点的带权无向图,计算最小生成树
代码
对于判断选定边所连的两个点是否一个在树内,一个在树外,可以用 vis 数组来判断
而选择权值最小的边,则借用优先队列实现
#include <bits/stdc++.h>
using namespace std;
#define M 500000
#define N 100000
int n, m, res = 0;
//链式前向星建图
struct Node{
int x, v, next;
}edge[M*2+1];
int head[N+1], cnt = 0, dis[N+1];
void addedge(int x, int y, int z){
edge[++cnt].x = y;
edge[cnt].v = z;
edge[cnt].next = head[x];
head[x] = cnt;
}
//边权优先队列
struct ty{
int x, len;
//比大小重载
bool operator < (const ty &a) const{
return len > a.len;
}
}temp;
priority_queue<ty>q;
bool vis[N+1];
void prim()
{
//以初始点1更新队列
vis[1] = true;
for(int i = head[1]; i != -1; i = edge[i].next){
temp.x = edge[i].x;
dis[edge[i].x] = edge[i].v;
temp.len = edge[i].v;
q.push(temp);
}
while(!q.empty()){
//将当前边权最小的可连接点加入树中
//并更新最小生成树代价
ty tmp = q.top(); q.pop();
if(vis[tmp.x]) continue;
vis[tmp.x] = true;
res += tmp.len;
//将新加入点所连接的边放进队列
for(int i = head[tmp.x]; i != -1; i = edge[i].next){
if(vis[edge[i].x]) continue;
//大于等于之前所选边权的新边权无需放入
if(dis[edge[i].x] <= edge[i].v) continue;
//
tmp.x = edge[i].x;
dis[edge[i].x] = edge[i].v;
tmp.len = edge[i].v;
q.push(tmp);
}
}
//输出
cout <<res;
}
int main(void)
{
memset(head, -1, sizeof(head));
memset(dis, 0x3f, sizeof(dis));
memset(vis, false, sizeof(vis));
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++){
int a, b, v;
scanf("%d%d%d", &a, &b, &v);
addedge(a, b, v);
addedge(b, a, v);
}
prim();
return 0;
}
Kruskal算法
- 它与prim算法同样都是利用了贪心的策略,只是krusal算法并不会利用树内和树外的分类来选边,而是利用并查集
- 时间复杂度 O(e log2e),适和稀疏图
基本过程
-
将所有的边按大小进行排序(
优先队列
) -
选择一条之前 未选择过的 且 权值最小的 边
-
利用并查集判断选出的边所连接的两个点当前是否在一个集合里,如果不在,就将该边加入最小生成树,并更新并查集信息
-
重复2、3操作直到所有的结点生成一颗树
实现
目标
读入一个含有 n 个点的带权无向图,计算最小生成树
代码
#include <bits/stdc++.h>
using namespace std;
#define M 500000
#define N 100000
int n, m, res = 0;
//边结构体
struct Node{
int x, y, v;
}edge[M+1];
//按边权值升序排列
bool cmp(Node a, Node b){
return a.v < b.v;
}
//并查集查找父节点以及路径压缩
int fa[N+1];
int find(int x){
if(fa[x] == x)
return x;
else
return fa[x] = find(fa[x]);
}
void Kruskal()
{
//初始化并查集
for(int i = 1; i <= n; i++) fa[i] = i;
//按边权排序
sort(edge+1, edge+1+m, cmp);
//依次拿边
for(int i = 1; i <= m; i++){
//判断选定边所连的两个点是否已经在树里
int fx = find(edge[i].x);
int fy = find(edge[i].y);
if(fx == fy) continue;
res += edge[i].v;
fa[fx] = fy;
}
cout <<res;
}
int main(void)
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++){
int a, b, v;
scanf("%d%d%d", &edge[i].x, &edge[i].y, &edge[i].v);
}
Kruskal();
return 0;
}