最小生成树(Prim和Kruskal)

166 阅读2分钟

最小生成树定义

最小生成树是一个无向图问题,在无向图中,连通而且不含有环的图称为树。最小生成树的定义:所有点连通的边权总和值最小的树。

最小生成树算法

图最基本的元素的点和边,所以有两个方法可以构造最小生成树,两个方法都是利用贪心的思想,prim算法是“距离最小生成树集合最近的点一定在最小生成树上”,kruskal算法是“最短的边一定在最小生成树上”。

Prim算法

算法步骤

对点进行贪心操作,设最小生成树的集合为U,开始最小生成树的集合为空。

  1. 首先任意取一点s放入集合U中。

  2. 找离集合中的点最近的点v。

  3. 然后更新v的邻居结点。

  4. 继续上述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算法

算法步骤

对边进行贪心操作。

  1. 从最短的边开始,把他加入到集合U中。

  2. 在剩下的边中找到最小的边,加入U中。

  3. 继续上面的操作。

注意: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;
}