树:最小生成树-两种算法

先来说说什么是树。

树实际上是图的一种,当一个有N个点的无向连通图,只有N-1条边时,就是一棵树,即树中不会有环出现;所以对于一个图,删除某些环中的某条边,使得该图成为一棵树,那么这棵树就称为生成树。

而最小生成树的意思就是,给定有n个顶点的带权图G(E,V),找到一棵生成树,求该生成树的边权和。


Kruskal算法:

算法步骤:

      1.构造一个有n个顶点的无边子图;

      2.从原图选择边权最小的边加入该子图,直至子图成为一棵树;

      3.边能加入子图的条件是,边的两个端点u,v还未连通,Kruskal算法中运用并查集的查询来询问两个顶点是否连通;

      Kruskal算法的本质是,通过树的合并(不断加边,构成子树),来构建完整的生成树。

Kruskal+邻接表 模板如下:

//复杂度O(ElogE),E为边数
const int INF=10e8;
const int MAXN=110;
const int MAXM=MAXN*MAXN;

struct edge
{
    int u,v,cost;
    bool operator < (const edge &a)const
    {
        return cost<a.cost;
    }
};

edge E[MAXN];
int couEdge;  //记得初始化couEdge=0,然后进行加边操作
int fa[MAXN];

int Find(int x)
{
    if(fa[x]==-1)  //important!!
        return x;
    fa[x]=Find(fa[x]);
    return fa[x];
}

int Kruskal(int n)
{
    int ans=0int cou=0;  //important!! 建好子图之后,每给子图加上一条边,cou+1
    int u,v,cost,t1,t2;
    
    memset(fa,-1,sizeof(fa));  //important!!
    sort(E,E+couEdge);
    for(int i=0;i<couEdge;i++)
    {
        u=E[i].u;v=E[i].v;cost=E[i].cost;
        t1=Find(u);t2=Find(v);  //t1,t2分别代表u,v的父节点
        
        if(t1!=t2)
        {
            ans+=cost;
            fa[t1]=t2;
            cou++;
        }
        if(cou==n-1)
            break;
    }
    if(cou<n-1)
        return -1;  //表示该图不连通
    return ans;
}
//加边操作
void add_edge(int u,int v,int cost)
{
    E[couEdge].u=u;E[couEdge].v=v;E[couEdge].cost=cost;
}


Prim算法:

      前面说了,Kruskal算法是通过树的合并来构建生成树,而现在要说的Prim算法,其本质则是,从一个顶点扩展成生成树,复杂度O(N2)。

【对比:Kruskal和Prim的异同】

            同:都是贪心思想;都是选择边权最小的边;

            异:Kruskal是优先选择边权最小的边,Prim是从点集出发选择边权最小的边;

                  //当题目中的数量比顶点的时候,用Prim速度比较快。


Prim算法的基本步骤:

      1.初始化点集 V={x};

      2.找到边(u,v)满足:u∈点集V,v不∈点集V

      3.选取2.中满足条件的边中最小的一条加入生成树,并把v加入点集V,重复执行,直至原图所有的点都加入点集V,就得到了一棵最小生成树。

   Tips:关于如何快速找到可以添加到生成树的边:

                    可以维护一个数组lowcost[i…j]记录点集V到各个顶点的最小边,即可快速找到边,且每当有新的点加入点集V时,该数组都要更新一次

Prim+邻接矩阵 模板如下:

const int INF=10e8;
const int MAXN=110;

bool vis[MAXN];
int lowcost[MAXN];

int Prim(int cost[][MAXN],int n)
{//输入cost[][],n;且cost[][]的初始化要注意没连通的边存INF
    int ans=0;
    int minn,k;

    memset(vis,0,sizeof(vis));
    vis[1]=1;  //从点1开始扩展,找距离1最小的边扩展

    for(int i=1;i<=n;i++)
        lowcost[i]=cost[1][i];  //
    for(int i=1;i<=n-1;i++)  //循环n-1次,因为vis[1]=1
    {
        minn=INF;k=-1;
        for(int j=1;j<=n;j++)
            if(!vis[j]&&minn>lowcost[j])
            {
                minn=lowcost[j];
                k=j;
            }
        if(k==-1) break;  //不连通
        ans+=minn;vis[k]=1;  //再从k点开始扩展,准备更新lowcost[]
        for(int j=1;j<=n;j++)
            if(!vis[j]&&lowcost[j]>cost[k][j])
                lowcost[j]=cost[k][j];
    }
    return ans;
}
posted @ 2016-02-02 20:50  &ATM  阅读(1073)  评论(0编辑  收藏  举报
……