第76课 - 最小生成树(Prim)

1、最小生成树(Prim) 

        运营商的挑战 

            -在下图标出的城市间架设一条通信线路 

        要求: 

                任意两个城市间都能够通信 

                将架设成本降至最低 

                


        解决方案

                


        问题 

                如何在图中选择n-1条边使得n个顶点间两两可达, 

                            并且这n-1条边的权值之和最小? 

    

        最小生成树 

                -仅使用图中的n-1条边连接图中的n个顶点 

                -不能使用产生回路的边 

                -各边上的权值的总和达到最小 


        寻找最小生成树

            



        最小生成树算法步骤(Prim) 

                1. 选择某一顶点v。作为起始顶点,使得T = { v0 }, F = { v1, v2, … vn }, E = { } 

                2. 每次选择一条边,这条边是所有(u, v)中权值最小的边,且u ∈ T, v ∈ F 

                3. 修改T, F, E: 

                                    T = T + { v },   F = F - { v },   E = E + { (u, v) } 

                4. 当 F ! = NULL时,且 (u, v)存在,转2; 否则,结束 


        最小生成树算法的原材料 

                    

                            如果T集合到F集合中同一个顶点的连 

                            接有多条,那么选取权值最小的连接。 


                


        注意事项 

            -最小生成树仅针对无向图有意义 

            -必须判断图对象是否能够看做无向图 


         问题: 

                 什么样的有向图能够看做无向图? 


                图类型(Graph)中的新增成员函数 

                        - virtual bool isAdjacent(int i, int j) = 0

                                判断在当前图中顶点i到顶点j是否邻接 

                        - bool asUndirected() ; 

                                 判断当前的有向图是否能够看做无向图 


2、编程实验 

最小生成树算法 Prim 

Graph.h

新增public成员

    virtual bool isAdjacent(int i, int j) = 0;  //判断i,j是否有连接

ListGraph.h

新增isAdjacent的实现

    bool isAdjacent(int i, int j)
    {
        return (0 <= i) && (i < vCount()) && (0 <= j) && (j < vCount()) && (m_list.get(i)->edge.find(Edge<E>(i, j)) >= 0);
    }

Matrixgrap.h

新增isAdjacent的实现

    bool isAdjacent(int i, int j)
    {
        return (0 <= i) && (i < vCount()) && (0 <= j) && (j < vCount()) && (m_edges[i][j] != NULL);
    }

Graph.h

新增public成员 

    bool asUndirected()
    {
        bool ret = true;

        for(int i=0;i<vCount();i++)
        {
            for(int j=0;j<vCount();j++)
            {
                if( isAdjacent(i, j) )
                {
                    ret = ret && isAdjacent(j, i) && (getEdge(i, j) == getEdge(j, i));
                }
            }
        }

        return ret;
    }

Graph新增prim算法

    SharedPointer< Array< Edge<E> > > prim(const E& LIMIT, const bool MINIMUM=true)    //返回一个智能指针,指向一个边的数组,
    {
        LinkQueue< Edge<E> > ret;   //E集合

        if( asUndirected() )    //可以看作无向图
        {
            DynamicArray<int> adjVex(vCount());     //记录cost中权值的对应顶点
            DynamicArray<bool> mark(vCount());        //标记顶点所属集合T,F
            DynamicArray<E> cost(vCount());          //T集合到F集合最小权值
            SharedPointer< Array<int> > aj = NULL;  //保存某个顶点邻接顶点数组
            bool end = false;                        //标记判断prim是否中断执行
            int v = 0;                                  //习惯从0顶点寻找最小生成树

            for(int i=0; i<vCount(); i++)
            {
                adjVex[i] = -1;
                mark[i] = false;
                cost[i] = LIMIT;    //初始保存理论上的最大权值
            }

            mark[v] = true;//标记初始顶点

            aj = getAdjacent(v);

            for(int j=0; j<aj->length(); j++)
            {
                cost[(*aj)[j]] = getEdge(v, (*aj)[j]);  //获取v到邻接所有顶点的权值 ,保存到cost数组
                adjVex[(*aj)[j]] = v;  //记录边
            }

            for(int i=0; i<vCount() && !end; i++)
            {
                E m = LIMIT;
                int k = -1; //记录相应最小值的顶点

                for(int j=0; j<vCount(); j++)   //遍历cost数组找最小权值
                {
                    if( !mark[j] && (MINIMUM ? (cost[j] < m) : (cost[j] > m)))    //本质获取连接的最小边,对应的顶点在F集合
                    {
                        m = cost[j];
                        k = j;
                    }
                }   //得到顶点号

                end = (k == -1);//是否找到合法最小权值

                if( !end )
                {
                    ret.add(Edge<E>( adjVex[k], k, getEdge(adjVex[k], k) ) );

                    mark[k] = true;

                    aj = getAdjacent(k);

                    for(int j=0; j<aj->length(); j++)
                    {
                        if( !mark[(*aj)[j]] && (MINIMUM ? (getEdge(k, (*aj)[j]) < cost[(*aj)[j]]) : (getEdge(k, (*aj)[j]) > cost[(*aj)[j]]) ) )//T集合到F集合新连接权值较小,记录到cost数组
                        {
                            cost[(*aj)[j]] = getEdge(k, (*aj)[j]);
                            adjVex[(*aj)[j]] = k;
                        }
                    }
                }
            }
        }
        else
        {
            THROW_EXCEPTION(InvalidOperationException,"Prim operation is for undirected graph only ...");
        }

        if( ret.length() != (vCount() - 1) )
        {
            THROW_EXCEPTION(InvalidOperationException,"No enough edge for prim operation ...");
        }

        return toArray(ret);
    }

main.cpp

#include <iostream>
#include "MatrixGraph.h"
#include "ListGraph.h"

using namespace std;
using namespace DTLib;

template< typename V, typename E >
Graph<V, E>& GraphEasy()
{
    static MatrixGraph<4, V, E> g;

    g.setEdge(0, 1, 1);
    g.setEdge(1, 0, 1);

    g.setEdge(0, 2, 3);
    g.setEdge(2, 0, 3);

    g.setEdge(1, 2, 1);
    g.setEdge(2, 1, 1);

    g.setEdge(1, 3, 4);
    g.setEdge(3, 1, 4);

    g.setEdge(2, 3, 1);
    g.setEdge(3, 2, 1);

    return g;
}

template< typename V, typename E >
Graph<V, E>& GraphComplex()
{
    static ListGraph<V, E> g(9);

    g.setEdge(0, 1, 10);
    g.setEdge(1, 0, 10);

    g.setEdge(0, 5, 11);
    g.setEdge(5, 0, 11);

    g.setEdge(1, 2, 18);
    g.setEdge(2, 1, 18);

    g.setEdge(1, 8, 12);
    g.setEdge(8, 1, 12);

    g.setEdge(1, 6, 16);
    g.setEdge(6, 1, 16);

    g.setEdge(2, 3, 22);
    g.setEdge(3, 2, 22);

    g.setEdge(2, 8, 8);
    g.setEdge(8, 2, 8);

    g.setEdge(3, 8, 21);
    g.setEdge(8, 3, 21);

    g.setEdge(3, 6, 24);
    g.setEdge(6, 3, 24);

    g.setEdge(3, 7, 16);
    g.setEdge(7, 3, 16);

    g.setEdge(3, 4, 20);
    g.setEdge(4, 3, 20);

    g.setEdge(4, 5, 26);
    g.setEdge(5, 4, 26);

    g.setEdge(4, 7, 7);
    g.setEdge(7, 4, 7);

    g.setEdge(5, 6, 17);
    g.setEdge(6, 5, 17);

    g.setEdge(6, 7, 19);
    g.setEdge(7, 6, 19);

    return g;
}

int main()
{
    Graph<int, int>& g = GraphEasy<int, int>();

    SharedPointer< Array< Edge<int> > > sa = g.prim(65535);

    int w = 0;

    cout<<"最小生成树"<<endl;

    for(int i=0; i<sa->length(); i++)
    {
        w += (*sa)[i].data;

        cout << (*sa)[i].b << " " << (*sa)[i].e << " " << (*sa)[i].data << endl;
    }

    cout << "Weight: " << w << endl;


    cout<<endl<<endl;

    cout<<"最大生成树"<<endl;

    Graph<int, int>& gc = GraphComplex<int, int>();

    SharedPointer< Array< Edge<int> > > sp = gc.prim(0, false);

    int ww = 0;

    for(int i=0; i<sp->length(); i++)
    {
        ww += (*sp)[i].data;

        cout << (*sp)[i].b << " " << (*sp)[i].e << " " << (*sp)[i].data << endl;
    }

    cout << "Weight: " << ww << endl;

    return 0;
}

                            


3、小结 

            最小生成树使得顶点间的连通代价最小 

            Prim算法通过顶点的动态标记寻找最小生成树 

            Prim算法的关键是集合概念的运用(T集合,F集合) 

            利用 Prim算法的思想也能寻找图的“ 最大生成树"


猜你喜欢

转载自blog.csdn.net/qq_39654127/article/details/80739244