算法——最小生成树

前言:最小生成树是在一个给定的无向图中求一棵树,这棵树包含无向图中的所有顶点,且树中的边都来自无向图中的边,并且要满足整棵树的边权之和最小。。

最小生成树的3个性质:

1、最小生成树是树,其边数等于顶点数减1,且不会有环

2、对于给定的图最小生成树可以不唯一,但是边权之和一定是唯一的。

3、其根节点可以是这棵树上的任何一个节点,题目中不做说明这默认根节点是0号顶点是根节点;但是为了最小生成树的唯一,题目中一般会给定根节点。

最小生成树有两种算法:第一个是prime(普里姆)算法,第二个是kruskal(克鲁斯卡尔)算法。

一、prime算法

1、prime算法的思想

       对图G设置一个集合S,存放已被访问的顶点,然后每次从集合V-S中选取与集合S的最短距离最小的一个顶点u加入集合S并进行访问,之后令顶点u为中介点,优化从u能到达的顶点v与集合S之间的最短距离。prime算法的思想和dijkstra算法思想基本一样,只不过prime算法中优化的是与集合S的最短距离,dijkstra算法中优化的是与起点s的最短距离。

2、prime算法的具体实现(邻接矩阵版)

      对比dijastra算法理解记忆.

const int maxv=1000;
const int inf=100000000;
 
int n,G[maxv][maxv];
int d[maxv];//表示顶点与集合S的最短距离
bool vis[maxv]={false};
 
int Prime()
{
    fill(d,d+maxv,inf);//初始化d数组
    d[0]=0;            //默认0号顶点为根节点

    int ans=0;//存放最小生成树的边权之和

    for(int i=0;i<n;i++)//循环n次
    {
        int u=-1,mina=inf;
        for(int j=0;j<n;j++)//每次找出未被访问的顶点中离起点最短距离最小的顶点
        {
            if(vis[j]==false&&d[j]<mina)
            {
                u=j;
                mina=d[j];
            }
        }
        
        if(u==-1) return;//若找不到,这说明剩下的顶点和起点s不连通或者说已经找完了所有顶点
        vis[u]=true;//若找到则进行标记
        
        ans+=d[u];

        for(int v=0;v<n;v++)//找从u出发能到达的顶点v,是否能优化d[v] 
        {
            if(vis[v]==false&&G[u][v]!=inf && G[u][v]<d[v])
                d[v]=G[u][v];
        }
        
    }
    return ans;
}

3、prime算法邻接表版

const int maxv=1000;
const int inf=100000000;

struct Node{
    int v,dis;
};
 
int n;
vector<Node> adj[maxv];
int d[maxv];
bool vis[maxv]={false};
 
int Prime()
{
    fill(d,d+maxv,inf);//初始化d数组
    d[0]=0;

    int ans=0;//与dijkstra的不同之处
           
    for(int i=0;i<n;i++)//循环n次
    {
        int u=-1,mina=inf;
        for(int j=0;j<n;j++)//每次找出未被访问的顶点中离起点最短距离最小的顶点
        {
            if(vis[j]==false&&d[j]<mina)
            {
                u=j;
                mina=d[j];
            }
        }
        
        if(u==-1) return;//若找不到,这说明剩下的顶点和起点s不连通或者说已经找完了所有顶点
        vis[u]=true;//若找到则进行标记
        
        ans+=d[u];
       
        for(int j=0;j<adj[u].size();j++)//与邻接矩阵访问不相同的地方,读者应仔细揣摩此处,
                                            对比理解记忆。
        {
            int v=adj[u][j].v;
            if(vis[v]==false&&adj[u][j].dis<d[v])
                d[v]=adj[u][j].dis;
        }
    }

    return ans;
}
 
 

二、kruskal算法

1、kruskal算法思想:

在初始状态时隐去所有的边,这样图中的每个顶点都是一个连通块。之后执行以下步骤:

       <1>对所有的边按边权大小进行排序

       <2>按边权从小到大测试所有的边,如果当前测试的边的两个顶点不在一个连通块内,这把这条边加入当前最小生成树中,               否则舍弃

       <3>执行步骤2直到最小生成树的边数等于顶点数减1或者测试完所有边时结束。如果边数小于顶点数减1这此图一定不连通

注意:上面的步骤中第二个步骤中怎么判断不在一个连通块中,又怎么加入到当前最小生成树中呢???不知道到这里大家有没有想到并查集这个很有意思的操作,刚好对应于步骤2的实现。此处引用博主zhangbajin的一篇博客,详细的讲解了并查集的应用,不懂的话一定要仔细看看,这也是此算法的关键之处。   https://blog.csdn.net/zhangbajin/article/details/83312619

2、kruskal算法的具体实现

struct edge{
    int u,v;
    int cost;
}E[maxv];

bool cmp(dege a,edge b)
{
    return a.cost<b.cost;
}

int father[N];
int findFather(int x)
{
    int a=x;
    while(x!=father[x];
        x=father[x];
    while(a!=father[a])//路径压缩
    {
        int z=a;
        a=father[a];
        father[z]=x;
    }
    return x;
}

int kruskal(itn n,int m)
{
    int ans=0,num_edge=0;
    for(int i=1;i<=n;i++)//初始化并查集的father[]数组
         father[i]=i;
    sort(E,E+m,cmp);
    for(int i=0;i<m;i++)
    {
        int fau=findFather(E[i].u);//判断是否在一个连通块内
        int fav=findFather(E[i].v);
        if(fau!=fav)
        {
            father[fau]=fav;
            ans+=E[i].cost;
            num_edge++;
            if(num_edge==n-1) break;
        }
    }
    if(num_edge!=n-1) return -1;
    else return ans;
}

三、prime算法和kruskal算法的对比:

      prime算法适用于边数较多,顶点数较少的稠密图,kruskal算法和prime算法相反,适用于定点数较多,边数较少的稀疏图。根据题目的具体要求选择合适的算法。

猜你喜欢

转载自blog.csdn.net/qq_38938670/article/details/83502818
今日推荐