最小生成树(ST)概念及构造

概念

图的生成树
它的一棵含有所有顶点的无环连通子图
特点:

  • 有n个顶点一定有,n-1条边。
  • 生成树是图的极小连通子图 (去掉一条边则非连通)。

分类:
深度优先生成树
广度优先生成树
image

最小生成树:
对于加权图(网) 的生成树中边的权重之和最小的生成树。
最小生成树可能不唯一

最小生成树作用:
修建道路时,利用最小生成树可以找到每个城市都能连通的最小代价。

构造最小生成树(MST)

MST性质:

  • 设N=(V,E)是一个连通网,U是顶点集V的一个非空子集。
  • 若边(u,v)是一条具有最小权值的边,其中u⋳U,v⋳V-U,则必存在一棵包含边(u,v)的最小生成树。

MST性质利用(贪心)
在生成树的构造过程中,图中n个顶点分属两个集合:

  • 已落在生成树上的顶点集:U
  • 尚未落在生成树上的顶点集:V-U
    接下来则应在所有连通U中顶点和V-U中顶点的边中选取权值最小的边。

普里姆算法(Prim)

思想:
每次将权值最小的边加入生成树中。
具体来说就是:

  • 将顶点分为未构建树的顶点集合U,和已构建树的顶点集合V;
  • 每次去找一个从U集合到V集合权值最小的边。

特点:
时间复杂度为 \(O(V^2)\),空间复杂度为 O(V)。V为顶点个数。
对图的顶点进行操作,适用于稠密图。

代码:


#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int maxn = 1e5 + 10;

struct edge /*构建小顶堆*/
{
    int ver;//末结点
    int dis;//路径长度
    bool operator<(const edge &t) const
    {
        return dis > t.dis;
    }
};


vector<edge> G[maxn];   /*graph*/
bool vis[maxn];         /*vis标记数组是否访问*/
int N, M;      /*vertex edge*/

void priority_queue_prim()
{
    priority_queue<edge> q;

/*维护一个与1号节点相连的边的集合,然后每次在其中找出最小的边*/
    for(edge k:G[1]) q.push(k);  
    
    vis[1]=true;
    int ret=0;/*统计权值*/
    int cnt=N-1;/*所有点是否访问*/


    while(q.size()&&cnt){
        edge tt=q.top();
        q.pop();
        if(vis[tt.ver]) continue;
        ret +=tt.dis;
        cnt--;
        vis[tt.ver]=true;
/*将这条边连接的点加入到1号节点中,然后用新加入节点连接出的几条边去更新优先队列*/
       for(edge k:G[tt.ver]){
           if(!vis[k.ver]) q.push(k);
       }
      
    }
    if(cnt) cout<<"-1"<<endl;
    else 
      printf("%d\n", ret);
}

int main()
{
    memset(vis, false, sizeof(vis)); /*初始化*/
    scanf("%d %d", &N, &M);

    for (int i = 0; i < M; ++i)
    {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        G[a].push_back(edge{b, w});
        G[b].push_back(edge{a, w});
    }

    priority_queue_prim();
    return 0;
}

优化代码

代码量变小了点。


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define PII pair<int, int>

const int maxn = 50010;
const int INF = 0x3f3f3f3f;
const int M = 1e9 + 7;
struct edge
{
    int ver;
    int dis;
    bool operator<(const edge &t) const
    {
        return dis > t.dis;
    }
};
vector<edge> G[maxn];
bool vis[maxn];
int n, m;
void prim()
{
    priority_queue<edge> Q;
    Q.push({1, 0});
    ll ret = 0;
    int cnt = n;
    while (Q.size() && cnt)
    {
        edge t = Q.top();
        Q.pop();
        if (vis[t.ver])
            continue;
        vis[t.ver] = 1;
        ret += t.dis;
        cnt--;
        for (edge k : G[t.ver])
        {
            if (!vis[k.ver])
                Q.push(k);
        }
    }
    if (cnt)
        cout << "-1" << endl;
    else
        cout << ret << endl;
}
int main()
{
    while (cin >> n >> m)
    {
        memset(vis, 0, sizeof vis);
        for (int i = 0; i < m; i++)
        {
            int a, c, b;
            cin >> a >> b >> c;
            G[a].push_back({b, c});
            G[b].push_back({a, c});
        }
        prim();
        
         for (int i = 1; i <= n; i++)
         {
           vector<edge>().swap(G[i]);
         }
    }
    return 0;
}

题目:

数据结构实验之图论九:最小生成树
答案: 优化代码就是。

克鲁斯卡尔算法(Kruskal)

克鲁斯卡尔算法
思想:
每次选择权重最小的边来构造最小生成树。
具体来说就是:

  • 首先做个拥有全部顶点但是没有边的图T;
  • 每次将权重最小的边加入最小生成树,并且舍去与已经加入的边形成环的边;
  • 直到树中有V-1到边为止。
    特点:
    时间复杂度为 O(ElogE)。空间复杂度为O(E)。E为边数。
    对图的边进行操作,因此它适用于稀疏图。

代码

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010, M = 200010, INF = 0x3f3f3f3f;

int n, m;
int p[N];

struct Edge
{
    int a, b, w;

    bool operator< (const Edge &W)const
    {
        return w < W.w;
    }
}edges[M];

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int kruskal()
{
    sort(edges, edges + m);

    for (int i = 1; i <= n; i ++ ) p[i] = i;    // 初始化并查集

    int res = 0, cnt = 0;
    for (int i = 0; i < m; i ++ )
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;

        a = find(a), b = find(b);
        if (a != b)
        {
            p[a] = b;
            res += w;
            cnt ++ ;
        }
    }

    if (cnt < n - 1) return INF;
    return res;
}

int main()
{
    scanf("%d%d", &n, &m);

    for (int i = 0; i < m; i ++ )
    {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        edges[i] = {a, b, w};
    }

    int t = kruskal();

    if (t == INF) puts("impossible");
    else printf("%d\n", t);

    return 0;
}

算法比较

稀疏图

算法名 普里姆算法 克鲁斯卡尔算法
算法思想 选择点 选择边
时间复杂度 o(n2)(n为顶点数) O(eloge)(e为边数)
适应范围 稠密图 稀疏图
posted @ 2022-02-08 16:46  kingwzun  阅读(421)  评论(0编辑  收藏  举报