有关最小生成树的一些心得

求最小生成树的两种算法:

1.Kruskal算法(本质是贪心)

1. 把图中的所有边按代价从小到大排序;

2.按权值从小到大选择边,所选的边连接的两个顶点ui,viui,vi,应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。

这种做法可以通过并查集来维护连通性,总体复杂度是O(m+并查集的玄学复杂度);

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
int n,m,i,j,u,v,total;
struct edge{
    int start,to;long long val;
}bian[2000005];
int f[100000];
long long ans;

int find(int x)//并查集部分
{
    if (f[x]==x) return x; else 
    {
        f[x]=find(f[x]);
        return f[x];
    }   
}

bool cmp(edge a,edge b)//结构体快排时用到的
{
    return a.val<b.val;
}

inline void kruskal()//最小生成树
{

    for(int i=1;i<=m;i++)
    {
        u=find(bian[i].start);
        v=find(bian[i].to);
        if(u==v) continue;//判断在不在同一个并查集里面,在就下一个循环
            ans+=bian[i].val;//不在,就加上
            f[u]=v;//连接两个并查集
            total++;
            if(total==n-1) break;//当形成了最小生成树后,退出(之后做的也没用了)
    }
} 
int main()
{
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++) f[i]=i;
    for(i=1;i<=m;i++)
    {
        scanf("%d%d%d",&bian[i].start,&bian[i].to,&bian[i].val);
    }
    sort(bian+1,bian+m+1,cmp);//快排边长
    kruskal();
    printf("%d",ans);
    return 0;
}

2.Prim算法(本质还是贪心)

每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。

时间复杂度是O(n^n);

但注意,每次寻找最短的一条边的时候,我们可以用堆来快速维护,使复杂度降至O((n+m)logm);

#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define R register int
using namespace std;

int k,n,m,cnt,sum,ai,bi,ci,head[5005],dis[5005],vis[5005];

struct Edge
{
    int v,w,next;
}e[400005];

void add(int u,int v,int w)
{
    e[++k].v=v;
    e[k].w=w;
    e[k].next=head[u];
    head[u]=k;
}

typedef pair <int,int> pii;
priority_queue <pii,vector<pii>,greater<pii> > q;

void prim()
{
    dis[1]=0;
    q.push(make_pair(0,1));
    while(!q.empty()&&cnt<n)
    {
        int d=q.top().first,u=q.top().second;
        q.pop();
        if(vis[u]) continue;
        cnt++;
        sum+=d;
        vis[u]=1;
        for(R i=head[u];i!=-1;i=e[i].next)
            if(e[i].w<dis[e[i].v])
                dis[e[i].v]=e[i].w,q.push(make_pair(dis[e[i].v],e[i].v));
    }
}

int main()
{
    memset(dis,127,sizeof(dis));
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    for(R i=1;i<=m;i++)
    {
        scanf("%d%d%d",&ai,&bi,&ci);
        add(ai,bi,ci);
        add(bi,ai,ci);
    }
    prim();
    if (cnt==n)printf("%d",sum);
    else printf("orz");
}

例题:洛谷P1265 公路修建

#include <bits/stdc++.h>
using namespace std;
double x[5005],y[5005];
double dis[5005],ans;
bool v[5005];
double query(double x,double x1,double y,double y1)
{
    return (x-x1)*(x-x1)+(y-y1)*(y-y1);
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1; i<=n; i++) scanf("%lf%lf",&x[i],&y[i]);
    memset(dis,0x7f,sizeof(dis));
    dis[1]=0;
    for(int i=1;i<=n;i++){
        int k=0;
        for (int j=1; j<=n; j++) if(!v[j]&&dis[j]<dis[k]) k=j;
        v[k]=1;
        for (int j=1; j<=n; j++) if(!v[j]&&query(x[k],x[j],y[k],y[j])<dis[j]) dis[j]=query(x[k],x[j],y[k],y[j]);          
    }
    for(int i=1;i<=n;i++) ans+=sqrt(dis[i]);
    printf ("%.2lf\n",ans);
    return 0;
}

从策略上来说,Prim算法是直接查找,多次寻找邻边的权重最小值,而Kruskal是需要先对权重排序后查找的~

所以说,Kruskal在算法效率上是比Prim快的,因为Kruskal只需一次对权重的排序就能找到最小生成树,而Prim算法需要多次对邻边排序才能找到~

prim:该算法的时间复杂度为O(n2)。与图中边数无关,该算法适合于稠密图。

kruskal:需要对图的边进行访问,所以克鲁斯卡尔算法的时间复杂度只和边又关系,可以证明其时间复杂度为O(eloge)。适合稀疏图

一句话来说,prim适合对于点进行运算,kruskal适合对于边进行运算;、

猜你喜欢

转载自www.cnblogs.com/kamimxr/p/11523020.html
今日推荐