前言:最小生成树是在一个给定的无向图中求一棵树,这棵树包含无向图中的所有顶点,且树中的边都来自无向图中的边,并且要满足整棵树的边权之和最小。。
最小生成树的3个性质:
1、最小生成树是树,其边数等于顶点数减1,且不会有环
2、对于给定的图最小生成树可以不唯一,但是边权之和一定是唯一的。
3、其根节点可以是这棵树上的任何一个节点,题目中不做说明这默认根节点是0号顶点是根节点;但是为了最小生成树的唯一,题目中一般会给定根节点。
最小生成树有两种算法:第一个是prime(普里姆)算法,第二个是kruskal(克鲁斯卡尔)算法。
一、prime算法
1、prime算法的思想
对图G设置一个集合S,存放已被访问的顶点,然后每次从集合V-S中选取与集合S的最短距离最小的一个顶点u加入集合S并进行访问,之后令顶点u为中介点,优化从u能到达的顶点v与集合S之间的最短距离。prime算法的思想和dijkstra算法思想基本一样,只不过prime算法中优化的是与集合S的最短距离,dijkstra算法中优化的是与起点s的最短距离。
2、prime算法的具体实现(邻接矩阵版)
对比dijastra算法理解记忆.
const int maxv=1000;
const int inf=100000000;
int n,G[maxv][maxv];
int d[maxv];//表示顶点与集合S的最短距离
bool vis[maxv]={false};
int Prime()
{
fill(d,d+maxv,inf);//初始化d数组
d[0]=0; //默认0号顶点为根节点
int ans=0;//存放最小生成树的边权之和
for(int i=0;i<n;i++)//循环n次
{
int u=-1,mina=inf;
for(int j=0;j<n;j++)//每次找出未被访问的顶点中离起点最短距离最小的顶点
{
if(vis[j]==false&&d[j]<mina)
{
u=j;
mina=d[j];
}
}
if(u==-1) return;//若找不到,这说明剩下的顶点和起点s不连通或者说已经找完了所有顶点
vis[u]=true;//若找到则进行标记
ans+=d[u];
for(int v=0;v<n;v++)//找从u出发能到达的顶点v,是否能优化d[v]
{
if(vis[v]==false&&G[u][v]!=inf && G[u][v]<d[v])
d[v]=G[u][v];
}
}
return ans;
}
3、prime算法邻接表版
const int maxv=1000;
const int inf=100000000;
struct Node{
int v,dis;
};
int n;
vector<Node> adj[maxv];
int d[maxv];
bool vis[maxv]={false};
int Prime()
{
fill(d,d+maxv,inf);//初始化d数组
d[0]=0;
int ans=0;//与dijkstra的不同之处
for(int i=0;i<n;i++)//循环n次
{
int u=-1,mina=inf;
for(int j=0;j<n;j++)//每次找出未被访问的顶点中离起点最短距离最小的顶点
{
if(vis[j]==false&&d[j]<mina)
{
u=j;
mina=d[j];
}
}
if(u==-1) return;//若找不到,这说明剩下的顶点和起点s不连通或者说已经找完了所有顶点
vis[u]=true;//若找到则进行标记
ans+=d[u];
for(int j=0;j<adj[u].size();j++)//与邻接矩阵访问不相同的地方,读者应仔细揣摩此处,
对比理解记忆。
{
int v=adj[u][j].v;
if(vis[v]==false&&adj[u][j].dis<d[v])
d[v]=adj[u][j].dis;
}
}
return ans;
}
二、kruskal算法
1、kruskal算法思想:
在初始状态时隐去所有的边,这样图中的每个顶点都是一个连通块。之后执行以下步骤:
<1>对所有的边按边权大小进行排序
<2>按边权从小到大测试所有的边,如果当前测试的边的两个顶点不在一个连通块内,这把这条边加入当前最小生成树中, 否则舍弃
<3>执行步骤2直到最小生成树的边数等于顶点数减1或者测试完所有边时结束。如果边数小于顶点数减1这此图一定不连通
注意:上面的步骤中第二个步骤中怎么判断不在一个连通块中,又怎么加入到当前最小生成树中呢???不知道到这里大家有没有想到并查集这个很有意思的操作,刚好对应于步骤2的实现。此处引用博主zhangbajin的一篇博客,详细的讲解了并查集的应用,不懂的话一定要仔细看看,这也是此算法的关键之处。 https://blog.csdn.net/zhangbajin/article/details/83312619
2、kruskal算法的具体实现
struct edge{
int u,v;
int cost;
}E[maxv];
bool cmp(dege a,edge b)
{
return a.cost<b.cost;
}
int father[N];
int findFather(int x)
{
int a=x;
while(x!=father[x];
x=father[x];
while(a!=father[a])//路径压缩
{
int z=a;
a=father[a];
father[z]=x;
}
return x;
}
int kruskal(itn n,int m)
{
int ans=0,num_edge=0;
for(int i=1;i<=n;i++)//初始化并查集的father[]数组
father[i]=i;
sort(E,E+m,cmp);
for(int i=0;i<m;i++)
{
int fau=findFather(E[i].u);//判断是否在一个连通块内
int fav=findFather(E[i].v);
if(fau!=fav)
{
father[fau]=fav;
ans+=E[i].cost;
num_edge++;
if(num_edge==n-1) break;
}
}
if(num_edge!=n-1) return -1;
else return ans;
}
三、prime算法和kruskal算法的对比:
prime算法适用于边数较多,顶点数较少的稠密图,kruskal算法和prime算法相反,适用于定点数较多,边数较少的稀疏图。根据题目的具体要求选择合适的算法。