美文网首页
最小生成树

最小生成树

作者: lxr_ | 来源:发表于2022-09-02 13:07 被阅读0次

    生成树:

    • 所有顶点均由边连接在一起,但不存在回路的图。
    • 一个有n个顶点的连通图的生成树具有n-1条边;
    • 一个图可以有许多棵不同的生成树;
    • 生成树的顶点个数与图的顶点个数相同;
    • 生成树是图的极小连通子图,去掉一条边则非连通;
    • 生成树任意两个顶点间的路径是唯一的;
    • 含有n个顶点n-1条边的图不一定是生成树。

    无向图的生成树:

    • 设图G=(V,E)是个连通图,当从图任一顶点出发遍历图G时,将边集E(G)分成两个集合T(G)和B(G)。其中T(G)是遍历图时所经过的边和集合,B(G)是遍历图时未经过的边的集合。显然,G1(V,T)是图的极小连通子图,即子图G1是连通图G的生成树。由于遍历算法的不同可分为深度优先生成树和广度优先生成树。

    最小生成树:

    • 给定一个无向网图,在该网的所有生成树中,使得各边权值之和最小的那棵生成树称为该网的最小生成树,也叫最小代价生成树(Minimum Cost Spanning Tree)。
    • 构造最小生成树的算法有很多,多数都利用了MST的性质,即:
      设N=V(V,E)是一个连通网,U是顶点集V的一个非空子集。若边(u,v)是一条具有最小权值的边,其中u属于U,v属于V-U,则必然存在一棵包含边(u,v)的最小生成树。
      性质解释:
    • 图中n个顶点分为两个集合:
      (1)已落在生成树上的顶点集:U
      (2)尚未落在生成树上的顶点集:V-U
      (3)接下来应该在所有连通U中顶点和V-U中顶点的边中选取权值最小的边。

    普里姆算法(Prim)算法:

    (1)设N=(V,E)是连通网,TE是N上最小生成树中边的集合;
    (2)初始令U={u0},(u0属于V),TE={};
    (3)在所有u属于U,v属于V-U的边(u,v)属于E中,找一条代价最小的边(u0,v0);
    (4)将(u0,v0)并入集合TE,同时v0并入U;
    (5)重复上述操作直至U=V为止,则T=(V,TE)为N的最小生成树

    克鲁斯卡尔(Kruskal)算法:

    (1)设连通网N=(V,E),令最小生成树初始状态为只有n个顶点而无边的非连通图T=(V,{}),每个顶点自成一个连通分量。
    (2)在E中选取代价最小的边,若该边依附的顶点落在T中不同的连通分量上(即:不能形成环),则将此边加入到T中;否则,舍去此边,选取下一条代价最小的边。
    (3)依次类推,直至T中所有顶点都在同一连通分量上为止。

    两种算法对比:

    算法对比
    代码示例
    关于图的邻接矩阵结构创建可查看以前的文章https://www.jianshu.com/p/b50ba1b3c327
    MGraph.h
    //Prim算法生成最小生成树
    void MiniSpanTree_Prim(MGraph G);
    
    //克鲁斯卡尔算法
    struct Edge                     //定义边集数组结构
    {
        int begin;                  //边的起点
        int end;                    //边的终点
        int weight;                 //边的权重
    
        Edge operator=(const Edge& e)
        {
            begin = e.begin;
            end = e.end;
            weight = e.weight;
    
            return *this;
        }
    };
    
    //将邻接矩阵转换为边集数组并按照权值升序排序
    void MGraph2Edge(MGraph G, Edge edges[]);
    
    //Kruskal算法生成最小生成树
    void MiniSpanTree_Kruskal(MGraph G);
    
    //查找连线顶点的尾部下标
    int Find(int* parent, int f);
    

    MGraph.cpp

    //Prim算法生成最小生成树
    void MiniSpanTree_Prim(MGraph G)
    {
        //step1:初始化
        int adjvex[MAXVEX];                                 //保存最小生成树中边的一端顶点(其实是已在生成树中的顶点)
        int lowcost[MAXVEX];                                //保存相关顶点间边的权值
    
        lowcost[0] = 0;                                     //初始化第一个权值为0,即v0加入生成树,通过此数组记录已加入生成树的顶点与其它顶点之间的边的最小值
        adjvex[0] = 0;                                      //初始化第一个顶点下标为0,通过此数组记录已加入生成树的顶点
    
        for (int i = 1; i < G.numVertexes; i++)             //遍历除了v0顶点的其它顶点
        {
            lowcost[i] = G.arc[0][i];                       //记录顶点v0与其它边的权值
            adjvex[i] = 0;                                  //顶点v0
        }                                                   //初始化完成
    
        //step2:寻找下一个加入生成树的顶点
        int k;
        for (int i = 1; i < G.numVertexes; i++)             //遍历每个顶点
        {
            int min = MAXEDGE;                              //初始化最小权值为无穷大
    
            for (int j = 1; j < G.numVertexes; j++)         //遍历lowcost寻找最小权值
            {
                if (lowcost[j] && lowcost[j] < min)         //如果该顶点还未加入生成树
                {
                    min = lowcost[j];
                    k = j;                                  //找到最小权值边对应的顶点
                }
            }
    
            cout << "(" << adjvex[k] << ","                 //输出最小权值的边
                << k << ")" <<
                "(" << G.vexs[adjvex[k]] << ","                 //输出最小权值的边
                << G.vexs[k] << ")" << endl;
    
            //step3:更新已加入生成树的顶点与其它顶点之间的最小值
            for (int m = 1; m < G.numVertexes; m++)
            {
                if (lowcost[m] && G.arc[k][m] < lowcost[m]) //新加入的顶点与其它顶点之间的边的最小值是否足够小
                {
                    lowcost[m] = G.arc[k][m];
                    adjvex[m] = k;                          //新加入的顶点k与m之间的边为候选
                }
            }
        }
    }
    
    //克鲁斯卡尔算法
    //将邻接矩阵转换为边集数组并按照权值升序排序
    void MGraph2Edge(MGraph MG, Edge edges[])
    {
        int k = 0;                      //记录插入个数
        for (int i = 0; i < MG.numVertexes; i++)
        {
            for (int j = 0; j < i; j++)
            {
                int weight = MG.arc[i][j];
                if (weight > 0 && weight < MAXEDGE)
                {
                    Edge edge;
    
                    edge.begin = i;
                    edge.end = j;
    
                    edge.weight = weight;
                    if (k == 0)         //第一个直接插入
                    {
                        edges[k] = edge;
                    }
                    else
                    {
                        int n = k - 1;                      //最后一个元素的位置
                        if (weight < edges[n].weight)       //如果待插入元素小于已经排好序的有序数组中最后一个元素
                        {
                            for (; weight < edges[n].weight; n--)
                            {
                                edges[n + 1] = edges[n];
                            }
                        }
                        edges[n + 1] = edge;                //插入
    
                    }
                    k++;
                }
            }
        }
    }
    
    //Kruskal算法生成最小生成树
    void MiniSpanTree_Kruskal(MGraph G)
    {
        Edge edges[MAXEDGE] = { 0 };                        //边集数组
        int parent[MAXVEX] ;                            //用于判断是否形成环
        for (int i = 0; i < G.numVertexes; i++)
        {
            parent[i] = -1;
        }
    
        MGraph2Edge(G, edges);                              //计算边集数组并按照权值升序排序
    
        for (int i = 0; i < G.numEdges; i++)                //遍历每一条边
        {
            int n = Find(parent, edges[i].begin);           //寻找此边的顶点begin是否在生成树中
            int m = Find(parent, edges[i].end);             //寻找此边的顶点end是否在生成树中
    
            if (n != m)                                     //不相等说明此边没有与现有生成树形成环路,两个顶点在不同的连通分量上
            {
                parent[n] = m;                              //将此边的结尾顶点放入下标为起点的parent中,表示顶点n,m已经在生成树集合中,
                cout << "(" << edges[i].begin << "," << edges[i].end << ")  " << edges[i].weight << endl;
            }
        }
    }
    
    //查找连线顶点的尾部下标,判断顶点f是否已经连接到生成树中
    int Find(int* parent, int f)
    {
        while (parent[f] > -1)
        {
            f = parent[f];
        }
        return f;
    }
    

    main.cpp

    #include <iostream>
    #include "MGraph.h"
    #include "ALGraph.h"
    
    using namespace std;
    
    extern bool* visited, * visited1;
    
    int main(int argc, char** argv)
    {
        //1.邻接矩阵
        MGraph MG;
        CreateMGrah(MG);
    
        for (int i = 0; i < MG.numVertexes; i++)    //对于连通图,只执行一次
        {
            if (!visited[i])
            {
                DFS(MG, i);                         //深度优先搜索遍历
                //BFS(MG, 0);                       //广度优先搜索遍历
            }
        }
    
        cout << "\nPrim 最小生成树:" << endl;
        /*
        A D 1
        A E 3
        A B 7
        B C 5
        B E 2
        C D 6
        C E 8
        D E 4
        */
        MiniSpanTree_Prim(MG);
    
        cout << "\nKruskal 最小生成树:" << endl;
        //Edge edges[MAXEDGE] = { 0 };
        //MGraph2Edge(MG, edges);
        MiniSpanTree_Kruskal(MG);
    
        return 0;
    }
    

    相关文章

      网友评论

          本文标题:最小生成树

          本文链接:https://www.haomeiwen.com/subject/vonenrtx.html