十二、图的算法入门--(2)最小生成树---Prim算法实现

摘自计蒜客: http://www.jisuanke.com/course/35/7551
讨论图论的一个经典问题——最小生成树。什么是最小生成树呢?我们先来看这样一个问题:

有 n 座城市,现要给城市间铺设高铁,使得任意两座城市之间都可以高铁到达。现已知任意两座城市之间铺设高铁的费用,求问如何铺设可以使得总费用最小。
这就是一个经典的最小生成树问题。首先可以把该场景看成是一个带权图,城市就是图中的顶点,边的权值就是城市间铺设高铁的费用。另外我们可以知道n个顶点,只需要n-1条边就能让任意两点连通。
那么问题就转化成了:如何从一个带权图中抽出一棵生成树,使得边权值和最小,这棵生成树就叫做最小生成树。常见的求解最小生成树的算法有Prim算法和Kruskal算法。
先来学习Prim算法。首先我们定义带权图 G 的顶点集合为V,接着我们再定义最小生成树的顶点集合为 U,初始集合 U为空。接着执行以下操作:

1.首先我们任选一个顶点 x,加入集合 U,并记录每个顶点到当前最小生成树的最短距离。

2.选择一个距离当前最小生成树最近的、且不属于集合 U 的顶点 v(如果有多个顶点 v,任选其一即可),将顶点 v 加入集合 U,并更新所有与顶点 v 相连的顶点到当前最小生成树的最短距离。

重复第二步操作,直至集合 U 等于集合 V。

最小生成树构造完毕,集合 U 记录了最小生成树的所有边。

举例模拟Prim算法如下:


通过模拟,最终可以得到该图对应的最小生成树,如下图所示:


分析算法过程,Prim算法的思想类似贪心策略,每次都会选择一条与当前最小生成树相连且边权值最小的点。Prim算法的时间

复杂度O(V2),V为图G顶点总个数,如果加上堆优化的话,可以把时间复杂度降到O(VlogV + E),其中E为图G的总边数。Prim算法

一般应用于边较为稠密的图,也就是顶点较少、而边较多的图。

算法实现://目标是从起点v出发,求解一个无向图的最小生成树上的所有边权的和。

#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;

const int INF = 0x3f3f3f3f;

struct Edge {
    int vertex, weight;//分别表示这条边的另一个端点和边的权重
};

class Graph {
private:
    int n;
    bool * visited;
    vector<Edge> * edges;
public:
    //用来标记每个顶点距离生成树上所有点的最短距离。
    //Prim算法就是每次找到距离生成树最近的顶点,并将它加入到生成树上。
    int * dist; 
    Graph (int input_n) {
        n = input_n;
        edges = new vector<Edge>[n];
        dist = new int[n];
        visited = new bool[n];
        memset(visited, false, n * sizeof(bool));
        memset(dist, 0x3f, n * sizeof(int));
    }
    ~Graph() {
        delete[] dist;
        delete[] visited;
        delete[] edges;
    }
    void insert(int x, int y, int weight) {
        edges[x].push_back(Edge{y, weight});
        edges[y].push_back(Edge{x, weight});
    }
    int prim(int v) {
        //[1]初始化变量
        int total_weight = 0;
        dist[v] = 0;
        
        //[2]不断找到距离生成树最近的顶点,并将它插入到当前生成树中
        for (int i=0; i<n; ++i) {
            int min_dist=INF, min_vertex;
            //从n个顶点中找到最近的顶点
            for(int j=0; j<n; ++j) {
                //[3]对于每个枚举到的顶点,如果之前没有加入到生成树,且距离小于当前的最小值,
                //就用它来更新最小值。首先用dist数组中对应的结果更新min_dist,然后将顶
                //点编号保存到min_vertex中。
                if(!visited[j] && dist[j] < min_dist) {
                    min_dist = dist[j];
                    min_vertex = j;
                }
            }
            //[4]将min_vertex插入到生成树中。首先将min_dist累加到生成树的边权和total_weight
            //中,并将标记顶点是否在生成树上的visited数组对应元素设为1,表示已插入生成树。
            total_weight += min_dist;
            visited[min_vertex] = 1;
            
            //[5]枚举min_vertex的每条边,枚举变量命名为j,类型为Edge的引用。
            for(Edge &j: edges[min_vertex]) {
                //对于每条边,如果另一个端点还未插入到生成树中,
                //并且当前枚举的边的边权比它到生成树
                //的最小距离要小,那么就用这条边的边权更新它到生成树的最小距离吧。
                if (!visited[j.vertex] && j.weight < dist[j.vertex]) {
                    dist[j.vertex] = j.weight;
                }
            }
        }
        return total_weight;
    }
};


int main() {
    int n, m;
    cin >> n >> m;
    Graph g(n);
    for (int i = 0; i < m; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        g.insert(a, b, c);
    }
    cout << g.prim(0) << endl;
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值