最小生成树之Prim算法

通俗的说,一个图的最小生成树包含该图的所有顶点,且树中所有边的权值之和最小。若图中顶点数为n,则它的生成树含有n-1条边。对于生成树而言,若砍去它的一条边,则会变成非连通图,若加上一条边,则会形成一个回路。比较正式的定义:连通图的最小生成树是包含图中全部顶点的一个极小连通子图。

Prim算法思想:
假设 N = { V , { E } } N=\{V,\{E\}\} N={V,{E}}是连通网, T E TE TE N N N上最小生成树的边集合,Prim算法将图中所有顶点分为两类,一类为集合 U U U,该集合中的点表示该点已经被选取为生成树上的一个点,另一类为集合 V − U V-U VU,表示还未被选取为生成树中的点,其中 V V V为图中所有点的集合。算法可以选取集合 V V V中的任意一个顶点初始化集合 U U U,即 U = { u 0 } ( u 0 ∈   V ) U=\{ u_0\}(u_0\in_\ V) U={u0}(u0 V)。考虑集合 U U U与集合 V − U V-U VU中相连的各条边,选取其中权值最小的那一条边 ( u , v ) ∈   E , u ∈   U , v ∈   V − U (u,v)\in\ E,u\in_\ U,v\in\ V-U (u,v) Eu U,v VU,其中 E E E为图中的边集,将点 v v v加入集合 U U U,边 ( u 0 , v 0 ) (u_0,v_0) (u0,v0)并入集合 T E TE TE,重复上诉过程直至 U = V U=V U=V为止。此时 T E TE TE中必有 n − 1 n-1 n1条边,则 T = ( V , { T E } ) T=(V,\{TE\}) T=(V,{TE}) N N N的最小生成树。

在这里插入图片描述

代码实现
假设图中一共有V个顶点,图采用邻接矩阵的存储方式。设置两个辅助数组lowcost[V]和adjvex[V](大小都为V)。lowcost[i]存储了集合 U U U上所有点,到顶点 i ∈   V − U i\in\ V-U i VU的最小代价(权值),这样每次选取最小权值的边时,只需要选取lowcost数组中最小值元素代表的边即可!adjvex[i]存储了lowcost[i]代表的最小权值边的始点(从哪个点到达点i),假设lowcost[5]=10,adjvex[5]=1,则表示集合 U U U上的所有点,连接到5号顶点可能有多条边,其中权值最小的边为10,且权值最小的边的起始顶点存储在adjvex上,为1号顶点。

#include "iostream"
#include "vector"
#include "climits"
#include "stdio.h"

using namespace std;
const int MAX_VERTEX_NUM = 100;
const int INF = INT_MAX;
int N;//图中的顶点数
int lowcost[MAX_VERTEX_NUM], adjvex[MAX_VERTEX_NUM];


void MinSpanTree_PRIM(vector<vector<int> > &map, int u)
{
    for (int i = 1; i <= N;i++){//初始化lowcost,和adjvex两个数据结构,集合U={u}开始
        if(i==u)
        {   
            lowcost[i] = 0;//表示开始开始点 
            adjvex[i] = 0;
        }
        else
        {   
            lowcost[i] = map[u][i];
            adjvex[i] = u;
        }
    }

    for (int i = 1; i < N;i++)//循环N-1次,每次找到一个点加入集合U中
    {
        //以下代码为找到最小权值的边
        int min = INT_MAX;
        int index = -1;
        for (int j = 1; j <= N;j++)
        {
            if(lowcost[j]!=0 && lowcost[j] < min)
            {
                min = lowcost[j];
                index = j;
            }
        }

        printf("最小生成树的边有:(%d - > %d), 权值为%d.\n", adjvex[index], index, min);
        lowcost[index] = 0; //将该点加入集合U

        //因为有新的点加入集合U,下面的for循环更新lowcost和adjvex两个数据结构
        for (int m = 1; m <= N;m++)
        {
            if(m!=index && lowcost[m]!=0 && map[index][m] < lowcost[m])//考虑新加入的点index到U-V中各个点m的权值是否更小,是则更新
            {
                lowcost[m] = map[index][m];
                adjvex[m] = index;
            }
        }
    }
}

int main(){
    int M;//边数
    cin >> N >> M;
    vector<vector<int> > map(N+1, vector<int>(N+1, INF));
    for (int i = 1; i <= M;i++){
        int u, v, w;
        cin >> u >> v >> w;
        map[u][v] = w;
        map[v][u] = w;//无向图
    }
    MinSpanTree_PRIM(map, 1);
    system("pause");
    return 0;
}

在这里插入图片描述
输入数据如下,代表上图所示的图

6 10
1 2 6
1 3 1
1 4 5
2 3 5
2 5 3
3 5 6
3 6 4
3 4 5
4 6 2
5 6 6

算法结果如下:
在这里插入图片描述
在这里插入图片描述

小结
Prim算法和dijkstra算法在算法思路步骤上很相似,都是由一个初始的集合开始生成树和找最短路,同时每次找到一个新的点加入集合后更新维护的数据结构。因此它们的代码结构也非常相似,外层一个大的for循环,每次经历一个循环表示找到一个点,循环内部有两个平行的for循环,内部的第一个for循环找最小边,内部的第二个for循环则是更新数据结构。所以该算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2) n n n为顶点个数,Prim算法时间复杂度与边数无关,适合于求边稠密的网的最小生成树。而克鲁斯卡尔算法恰恰相反,它的复杂度为 O ( e log ⁡ ( e ) ) O(e\log(e)) O(elog(e)),e为网中边的数目,因此它相对于普里姆(Prim)算法而言,适合于求边稀疏的网的最小生成树

参考资料:数据结构(C语言版).严蔚敏,吴伟民

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值