最小生成树定义
最小生成树是一个无向图问题,在无向图中,连通而且不含有环的图称为树。最小生成树的定义:所有点连通的边权总和值最小的树。
最小生成树算法
图最基本的元素的点和边,所以有两个方法可以构造最小生成树,两个方法都是利用贪心的思想,prim算法是“距离最小生成树集合最近的点一定在最小生成树上”,kruskal算法是“最短的边一定在最小生成树上”。
Prim算法
算法步骤
对点进行贪心操作,设最小生成树的集合为U,开始最小生成树的集合为空。
-
首先任意取一点s放入集合U中。
-
找离集合中的点最近的点v。
-
然后更新v的邻居结点。
-
继续上述2 3 过程,直到所有点都在集合u中。
代码
//prim
typedef pair<int,int> PII;//相当于一个结构体:first表示最短距离,second表示结点编号
int d[N];
int e[N],ne[N],w[N],idx;//e[i]位置i的边所指向的结点 ne[i]位置i所指向的下一个位置 w[i]位置i的边的权值
int h[N];//h[u]表示与结点u连接的第一条边的位置
void add(int a,int b,int c) //往图中添加边和结点
{
e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}
int d[N],st[N];
int n,m;
void init()
{
memset(d,0x3f,sizeof d);//初始化初始距离为无限大
memset(h,-1,sizeof h);//初始化h所指向的位置为-1
memset(st, 0, sizeof st);//st[i]=1表示结点i在集合中,=0表示不在集合中。
}
int prim()
{
priority_queue<PII,vector<PII>,greater<PII>> heap;
heap.push({0,1});
d[1]=0;
int ans=0,cnt=0;
while(!heap.empty())
{
auto t=heap.top();heap.pop();//取出当前到集合距离最小的结点
int dis=t.first,u=t.second;
//cout<<u<<endl;
if(st[u]) continue;//如果当前结点已在集合中,则跳到下一个距离最小的结点
ans+=dis;
cnt++;//集合中结点个数+1;
st[u]=true;//设置结点在集合中
for(int i=h[u];~i;i=ne[i])
{
int to=e[i];
if(d[to]>w[i])//更新邻居节点到集合的距离
{
d[to]=w[i];
heap.push({w[i],to});
}
}
}
if(cnt<n) return INF;//如果集合中的节点个数小于图的节点个数,说明不存在最小生成树。
return ans;
}
Kruskal算法
算法步骤
对边进行贪心操作。
-
从最短的边开始,把他加入到集合U中。
-
在剩下的边中找到最小的边,加入U中。
-
继续上面的操作。
注意:1.根据边的大小排序;2需要判断边的两个顶点是否在同一个集合中,使用并查集。
代码
int n,m;
struct edge
{
int u,v,w;
}e[N];
int p[N];//用于并查集
void init()
{
for(int i=1;i<=n;i++) p[i]=i; //初始化每个节点为一个单独的集合
}
int find(int a)
{
return a==p[a]?a:p[a]=find(p[a]);
}
bool cmp(edge a,edge b)
{
return a.w<b.w;
}
int kruskal()
{
init();
int ans=0,cnt=0;
sort(e,e+m,cmp);//根据边的大小升序排序
for(int i=0;i<m;i++)
{
int a=find(e[i].u),b=find(e[i].v);//查询边两个顶点所在的集合
if(a==b) continue;//如果在一个集合跳过
//cout<<e[i].w<<endl;
ans+=e[i].w;
p[a]=b;//合并集合
cnt++;//合并次数+1
}
if(cnt<n-1) return -1;//如果集合的合并次数<n-1次,说明没有最小生成树
return ans;
}