Prim算法

问题描述

有一张 \(n\) 个顶点、\(m\) 条边的无向图,且是连通图,求最小生成树。

Prim算法简析

\(Prim\) 算法是一种求最小生成树的算法。
设该图为 \(G = (V, E)\)。最小生成树即所求为 \(G_T = (V_T, E_T)\),因为图是连通的,所以最小生成树会覆盖所有的顶点,即 \(V == V_T\)\(G_T\)真子图 \(G_A = (V_A, E_A)\),构成一棵树(但 \(V_A < V\))。\(G - G_A\)\(G_B = (V_B, E_B)\), 其中 \(V_B = V - V_A\)
为了这棵树 \(G_A\) 继续生长为最小生成树 \(G_T\),依据贪心策略,我们要挑选权值最小的边加入 \(G_A\)。也就是说,从连接 \(G_A\)\(G_B\) 的边中挑选权值最小的边。

代码实现

#include <bits/stdc++.h>

using namespace std;

#define MAX 10              // 最大定点数
#define INF 1e8

// 定义边
typedef struct
{
    int to, worth;
} edge;

typedef pair<int, int> P;	    // first -- 顶点编号; second -- 该顶点到树的最短距离

vector<edge> G[MAX];            // 邻接链表
int d[MAX];                     // i 到树的最小距离
bool vis[MAX];					// i 是否在树中

// 最小堆
struct cmp
{
    bool operator()(const P &a, const P &b)
    {
        return a.second > b.second;
    }
};

int prim(void)
{
	// 初始化
    fill(begin(d), end(d), INF);
    d[1] = 0;									// 以 1 为根节点

    priority_queue<P, vector<P>, cmp> Q;
    Q.push(P(1, 0));
    int ans = 0;								// 最小生成树
    while (!Q.empty())
    {
        P p = Q.top();							// p -- 连接 G_A 和 G_B 的最短边
        Q.pop();
        int u = p.first;

        if (vis[u])								// u 是否在树中
            continue;							// 若在,则跳过该点

        ans += d[u];							// 若不在,加入树中
        vis[u] = true;							// u 已加入树

		// 遍历 u 为起点的每一条边
        for (int i = 0; i < G[u].size(); i++)
        {
            edge e = G[u][i];
            if (!vis[e.to] && d[e.to] > e.worth)  // 寻找未加入树且最短的边
            {
                d[e.to] = e.worth;				  // 更新 e.to 到 G_A 的最短路径
                Q.push(P(e.to, e.worth));
            }
        }
    }

    return ans;
}

  • 1、这里我们选择 \(1\) 为根节点。其实,可以选择任意顶点为根节点。因为 \(G\) 是连通图,所以最终的最小生成树一定覆盖所有的顶点 \(G.V\)
  • 2、该算法的关键是如何找到连接 \(G_A\)\(G_B\) 的最短边,这里,我们采用了最小优先队列(以边的权值为排序依据)。对于 \(G_A\) 的一个顶点 \(u\),遍历从它的所有边,选择符合要求的边加入 \(Q\)。这样,取出的边就是最短边了。
    我们来分析一下这里的要求 if (!vis[e.to] && d[e.to] > e.worth)。首先,要明确一点,虽然我们在挑选 \(u\) 的边,但其实,也在挑选边的终点 \(v\)。因此,顶点 \(v\) 必须不在 \(G_A\),这就是条件一。同时,\(e(u, v)\) 可能有重边,不止一条。所以,我们要挑选重边中 \(e(u, v).w\) 最小的,这就是条件二
    仔细看这个 \(if\) 语句,我们会发现,队列里可能依然有 \(e(u, v)\) 的重边,但肯定有 \(e(u, v)\) 的最短边。这里,我们这样处理重边:首先,最小优先队列保证了取出来的一定是最短边;其次,if (vis[u]) 这个判断语句跳过了剩下的重边(因为重边都有一个公共顶点 \(v\),然而,由于最小优先队列,它已经加入 \(G_A\))。
  • 3、我们可以发现,最小生成树算法 \(Prim\) 与最短路径算法 \(Dijkstra\) 很相似。其中,d[] 的意义并不一样。Prim\(d[u]=\) 顶点 \(u\)\(G_A\) 的最短路径。Dijkstra\(d[u]=\) 顶点 \(u\) 到起点 \(s\) 的最短路径。

posted @   ltign  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
点击右上角即可分享
微信分享提示