图的最小生成树

本文介绍了如何使用Prim和Kruskal算法求解图的最小生成树问题。Prim算法从一个节点开始,逐步扩展至整个图,每次添加权值最小的边。Kruskal算法则按边的权值排序,每次都选择最小的边,避免形成环路。两种方法都适用于解决网络最优化问题。
摘要由CSDN通过智能技术生成

一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。 最小生成树可以用prim(普里姆)算法或kruskal(克鲁斯卡尔)算法求出。本文以18448为例,介绍这两种算法的具体实现。

 

(一)Prim算法

设定图结构的结点集合V,边集合E。Prim算法设最小生成树的结点集合U开始为空集,任选V中任意一个结点u放入集合U,此时在集合U和集合V-U中选择满足条件的边,即边(x,y)的两个点x在集合U中,而y在集合V-U中,满足这个条件边中选择权值最小的,这条边一定属于一棵最小生成树,然后将结点y也放入集合U中。将这个过程重复n-1次,可得最小生成树的n-1条边。

算法的实现一些细节:(1)图结构算法几乎都需要使用标志数组v,用来标记已经访问过的结点。此处标志数组也能用于表示集合U(标记为1元素)和集合V-U(标记为0元素);(2)d数组用于辅助找到满足条件的最小的边,通过迭代处理的方式更新数组d。可通过上图和代码来理解处理过程。(如果想算法优化可用优先队列替换数组d

邻接矩阵写法:

#include <iostream>
#include <cstring>/**< memset函数 */
using namespace std;
long long n,m,v[2005],d[2005],e[2005][2005];
int getMin()
{
    int i,mini=0;/**< d[0]很大 */
    for(i=1;i<=n;i++)
        if(d[mini]>d[i]&&v[i]==0)
          mini=i;
    return mini;
}
long long prim()
{
    long long ans=0;
    memset(d,127/3,sizeof d);/**< 一个函数用于赋数组每个元素极大值 */
    d[1]=0;/**< 确保第一次选择出1号结点 */
    for(int j=1;j<=n;j++)
    {
        int minnode=getMin();
        v[minnode]=1;
        ans+=d[minnode];
        for(int i=1;i<=n;i++)
            d[i]=min(d[i],e[minnode][i]);
    }
    return ans;
}
int main()
{
    long long i,j,x,y,z;
    memset(e,127/3,sizeof e);/**< 邻接矩阵初始值为无穷大 */
    cin>>n>>m;
    for(i=1;i<=m;i++)
    {
        cin>>x>>y>>z;
        if(x==y)continue; //注意可能存在重边和自环。
        e[x][y]=e[y][x]=min(e[x][y],z);
    }
    cout<<prim();
    return 0;
}

邻接表写法:

#include <iostream>
#include <vector>
using namespace std;
struct node /**< 边的邻接点和权值,也可用pair */
{
    int adj,quan;
};
vector<node> e[2005];
int n,m,v[2005],d[2005];
int getMin()
{
    int i,minv=0;
    for(i=1;i<=n;i++)
        if(v[i]==0&&d[i]<d[minv])
          minv=i;
    return minv;
}
int prim()
{
    int i,j,ans=0;
    for(i=0;i<=n;i++)
        d[i]=1e9;
    d[1]=0;
    for(i=1;i<=n;i++)
    {
        int x=getMin();
        v[x]=1; ans+=d[x];
        for(j=0;j<e[x].size();j++)
        {
            int y=e[x][j].adj,z=e[x][j].quan;
            d[y]=min(d[y],z);
        }
    }
    return ans;
}
int main()
{
    int i,x,y,z;
    cin>>n>>m;
    for(i=1;i<=m;i++)
    {
        cin>>x>>y>>z;
        if(x==y) continue;
        e[x].push_back({y,z});
        e[y].push_back({x,z});
    }
    cout<<prim();
    return 0;
}

(二)Kruscal算法

克鲁斯卡尔算法应用“贪心思想”。每次选择权值最小的边,但有一个附加条件,这次选择的边不能和已有的边形成环。经过n-1次选择可得最小生成树。

算法实现细节:(1)需要对所有的边按权值进行排序;(2)如何确保新选择的边不和已有的边形成环 ?先说一个笨办法,将选中边存起来,写一个搜索算法来检查新选择的边的两个结点是否连通,不连通就可以留下。而标准做法是使用并查集。并查集实际上是一种非常简单的树结构,能处理数据元素集合的查询和合并(两个集合的合并)。此处我们先假定图中n个结点初始为n个集合(每个结点是一个集合),如果选中一条边(x,y),那么将x所属集合和y所属集合进行合并。并查集的写法不是本文的重点,因此不做过多阐述。

#include <iostream>
#include <algorithm>
using namespace std;
int n,m,f[2005];/**< f数组用于并查集 */
struct node
{
    int s,e,v;/**< s和e为两个邻接点,v为边权值 */
    bool operator <(const node y)const
    {
        return v<y.v;
    }
} e[20000];
int findx(int x)/**< 并查集的查操作 */
{
    return f[x]==x?x:f[x]=findx(f[x]);
}
int main()
{
    int i,j,sum=0;
    cin>>n>>m;
    for(i=1; i<=n; i++) /**< 并查集初始化 */
        f[i]=i;
    for(i=1; i<=m; i++)
        cin>>e[i].s>>e[i].e>>e[i].v;
    sort(e+1,e+m+1);
    for(i=1; i<=m; i++)
    {
        int r1=findx(e[i].s),r2=findx(e[i].e);
        if(r1!=r2)/**< 第i条边不会形成环,选中 */
        {
            sum+=e[i].v;
            f[r1]=r2;/**< 并查集的并操作,将r1集合并入r2集合 */
        }
    }
    cout<<sum;
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值