图的最小生成树prim算法详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Murdock_C/article/details/49559411

prim算法是求图的最小生成树的一种算法,它是根据图中的节点来进行求解,具体思想大概如下:
首先,将图的所有节点(我们假定总共有n个节点)分成两个集合,V和U。其中,集合V保存的是我们已经访问过的节点,集合U保存的是我们未曾访问的节点。prim算法第一步就是选定第一个节点放入集合V中(一般是第一个),然后查找V中节点和U中节点之间距离最小的那个路径,(假设U中节点w离V中的某个节点距离最近),将w放入V中,将w从U中删除,这样,直到U中所有节点都被放入V中,那么我们就找到了最小生成树。
因为prim算法每一步都需要遍历集合V和集合U中的所有节点,其时间复杂度为O(n*n),这是未经任何优化的prim算法时间复杂度。
一般,我们要对该算法进行优化,我们维护一个数组low[n],用来保存到每个节点的已知的最短路径是多少,例如,我们有图如下:这里写图片描述
首先,我们选定v1作为我们的初始节点,这时,low数组的大小分别为:
low[1] = 0;
low[2] = 6;
low[3] = 1;
low[4] = 5;
low[5] = INF;
low[6] = INF;
因为5和6从1无法到达,所以设定为无穷大。
此时,集合V中有节点{V1},集合U中有节点{V2,V3,V4, V5,V6},此时,通过条件判断,我们可以找到节点3离V1最近(通过low数组),然后,我们将节点V3放入集合V中,并从U中删除它,此时
集合V= {V1,V3}
集合U={V2,V4,V5,V6}
这时,我们需要对low数组进行更新。前面讲过,low数组是保存的我们已知的点到剩余节点的最短距离,因为此时V3加入了已知点集,所以需要根据V3到其它节点的距离进行更新;
low[1] = 0;//因为节点V1已经被访问过,所以不需要更新
low[2] = 5;//因为从节点V3到节点V2的距离为5,比上一个数据low[2] = 6要小,因此需要更新;
low[3] = 1;//因为节点V3已经被访问过,所以不需要更新;
low[4] = 5;//因为从节点V3到V4的距离和上一个数据low[4]=5一样,我们不更新;
low[5] = 6;//很明显,此时V3到V5的距离要比上一个数据low[5]=INF来的小,我们进行更新;
low[6] = 4;//道理同low[5];
此时,我们对low数组进行更新完毕,这样,下次就可以继续通过low数组来查找最距离最短的那个点。
prim核心代码如下:

#include <iostream>
using namespace std;

void prim()
{
    const int INF = 65536;//表示无穷大
    const int N = 100;//N为图中所有的节点数
    bool visited[N+1] = {false};//数组visited保存节点是否有被访问过,初始化为false
    int low[N+1];//保存已访问节点离未访问节点的最短距离
    int dist[N+1][N+1];//距离矩阵,用来保存图中节点之间的距离
    int result = 0;//最短距离
    int edge[N+1];//用来保存选择的点的依附节点
    int min = INF;
    int v;
    //////////////////初始化矩阵/////////////////////////
    for (int i=1; i<=N; ++i)
    {
        for (int j=1; j<=N; ++j)
        {
            if (i == j)
            {
                dist[i][j] = 0;
            }
            else
                dist[i][j] = INF;           
        }       
    }
    //////////////////输入边的信息//////////////////////
    int m, a, b, c;
    cout << "输入总的边数:";
    cin >> m;
    cout << "输入边的两个节点和边的长短:" << endl;
    for (int i=1; i<=m; ++i)
    {
        cin >> a >> b >> c;
        dist[a][b] = dist[b][a] = c;
    }

   //////////////////////////////////////////////////

    visited[1] = true;//我们选取第一个节点为初始节点
    low[1] = 0;
    edge[1] = 1;
    //初始化low和edge
    for (int i=2; i<=N; ++i)
    {
        low[i] = dist[1][i];
        edge[i] = 1;//初始化所有的节点依附节点为节点1
    }

    //循环N-1次,来求得最小生成树所花费的最短路径,因为有N个点,所以树有N-1个边
    for (int i=1; i<N; ++i)
    {
        min = INF;
        for (int j=1; j<=N; ++j)
        {
            if (!visited[j] && low[j]<min)//如果节点j没有被访问过并且当前路径小于min
            {
                min = low[j];
                v = j;
            }
        }
        //找到未被访问的节点V路径最短
        result += min;
        visited[v] = true;
        cout << "(" << v << "," << edge[v] << ")" << endl;//输出找到的这个点所依附的节点,相当于输出一条边
        //更新数组low和edge
        for (int i=1; i<=N; ++i)
        {
            if (!visited[i] && low[i]>dist[v][i])//如果节点i没有被访问过并且当前路径小于已知的路径low
            {
                low[i] = dist[v][i];
                edge[i] = v;//将节点i的新依附节点设置为找到的节点v
            }
        }

    }
    cout << result << endl;
    return ;//完毕后,得到的result即为最小生成树最短路径长度,数组edge即为选择的节点边的信息

}

int main()
{
    prim();
    system("pause");
    return 0;
}

若有错误,欢迎指出。另外,我们假定节点编号从1开始,而不是从0开始。

猜你喜欢

转载自blog.csdn.net/Murdock_C/article/details/49559411