📜  算法|图最小生成树|问题8(1)

📅  最后修改于: 2023-12-03 15:41:09.353000             🧑  作者: Mango

算法 | 图最小生成树 | 问题8

什么是最小生成树?

在图论中,给定一个加权无向连通图,生成树是指原图的一个连通子图,且所有顶点都在其中,它是一棵树。如果给每条边赋上一个权值,生成树的权值就是所有边的权值之和,最小生成树(MST,Minimum Spanning Tree)就是指生成树的权值最小的一颗。

一个图可以有多个不同的最小生成树,但如果每条边的权值都是唯一的,则最小生成树是唯一的。

如何求解最小生成树?

常见的求解最小生成树的算法有 Prim 算法和 Kruskal 算法。

Prim 算法

Prim 算法属于贪心算法,从一个初始点开始,通过不断加入连通着已有部分的最小边来构造生成树。具体步骤如下:

  1. 初始化生成树,将某个点加入生成树。
  2. 找到与已有部分相连的最小边,并将其加入生成树。
  3. 重复步骤 2,直到生成树包含所有顶点为止。

Prim 算法的时间复杂度为 O(n^2),其中 n 为顶点数,在稠密图中表现优异,在稀疏图中会更劣。

Kruskal 算法

Kruskal 算法也是贪心算法,它通过合并连通块来构造生成树,具体步骤如下:

  1. 将所有边按照权值从小到大排序。
  2. 从权值最小的边开始,如果边的两个端点不在同一个连通块中,那么将它们合并,并将该边加入生成树。
  3. 重复步骤 2,直到生成树中包含所有顶点为止。

Kruskal 算法的时间复杂度为 O(eloge),其中 e 为边数,在稀疏图中表现优异,在稠密图中会更劣。

如何实现最小生成树算法?

最小生成树算法可以用于无向加权图的建模与处理,可以通过 C++ 实现。其中,我们可以使用邻接矩阵或邻接表来实现图。

邻接矩阵实现
const int INF = 1000000000; // 表示无穷大

int main() {
    int n; // n 为顶点数
    int G[MAX_V][MAX_V]; // 邻接矩阵
    bool used[MAX_V]; // 记录顶点是否已经加入生成树
    int d[MAX_V]; // 记录生成树中与顶点最小边的权值

    // 初始化邻接矩阵和 used、d 数组
    for (int i = 0; i < n; i++) {
        fill(G[i], G[i] + n, INF);
        used[i] = false;
        d[i] = INF;
    }

    // 读入边
    for (int i = 0; i < m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        G[u][v] = w;
        G[v][u] = w;
    }

    // Prim 算法求解最小生成树
    long long res = 0; // 最小生成树的边权之和
    d[0] = 0; // 从任意一个点开始
    while (true) {
        int v = -1;
        // 找到未使用中与某个点距离最小的点
        for (int u = 0; u < n; u++) {
            if (!used[u] && (v == -1 || d[u] < d[v])) {
                v = u;
            }
        }
        if (v == -1) break;
        used[v] = true;
        res += d[v];

        for (int u = 0; u < n; u++) {
            d[u] = min(d[u], G[v][u]); // 更新最小距离
        }
    }

    cout << res << endl; // 输出生成树的总权值
    return 0;
}
邻接表实现
typedef pair<int, int> P; // 存储边权和终点的对

vector<P> G[MAX_V]; // 邻接表
bool used[MAX_V]; // 记录顶点是否已经加入生成树
int d[MAX_V]; // 记录生成树中与顶点最小边的权值

// Prim 算法求解最小生成树
long long Prim() {
    long long res = 0; // 最小生成树的边权之和
    priority_queue<P, vector<P>, greater<P>> que; // 存储以 d[i] 为关键字的优先队列
    fill(d, d + V, INF);
    d[0] = 0;
    que.push(P(0, 0));

    while (!que.empty()) {
        P p = que.top();
        que.pop();
        int v = p.second;

        if (used[v]) continue;
        used[v] = true;
        res += p.first;

        for (int i = 0; i < G[v].size(); i++) {
            int u = G[v][i].first;
            int w = G[v][i].second;
            if (d[u] > w) {
                d[u] = w;
                que.push(P(d[u], u));
            }
        }
    }

    return res;
}

int main() {
    int V, E; // V 为顶点数,E 为边数
    cin >> V >> E;

    // 读入边,构建邻接表
    for (int i = 0; i < E; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        G[u].push_back(P(v, w));
        G[v].push_back(P(u, w));
    }

    long long res = Prim();
    cout << res << endl; // 输出生成树的总权值
    return 0;
}
总结

最小生成树算法是图论中非常重要的一个问题,可以用于最优化设计和路由问题等应用领域。在实际应用中,可以灵活选择 Prim 或 Kruskal 算法来对不同类型的图结构进行求解,进一步优化算法效率。