码迷,mamicode.com
首页 > 其他好文 > 详细

图论 最小生成树

时间:2019-08-03 19:58:17      阅读:92      评论:0      收藏:0      [点我收藏+]

标签:最小   等于   格式   集合   思路   mst   连接   std   name   

图论 最小生成树

最小生成树 (MST)

? 最小生成树是一副连通加权无向图中一棵权值最小的生成树.

? 在一个有n个点的图中, 在所有边中选择n - 1条边, 连接所有n个点, 使这n - 1条边的权值和是所有方案中最小的.

? 最小生成树在算法竞赛中很少单独考察, 常与图论及其他树上算法结合, 因此对于最小生成树的掌握是十分必要的.

算法

Kruskal (克鲁斯卡尔)

基本思路

? 将所有点视为单独集合, 并将所有边按照边权从小到大排序, 然后顺序枚举每一条边. 如果这条边连接两个不同集合, 就将此边加入最小生成树中, 并合并这两个不同集合, 直至取了n - 1边.

? 对于不同点的关系,使用并查集维护.

代码

#include<bits/stdc++.h>

using namespace std;

    struct Edge
    {
        int dis, from, to;
    } edge[1000001];
    int ans;
    int cnt;
    int num_edge;
    int n, m;//点数, 边数
    int bin[1000001];
    void add(int from, int to, int dis)
    {
        edge[++cnt].dis = dis;
        edge[cnt].to = to;
        edge[cnt].from = from;
    }

    int find(int x)
    {
        if(bin[x] == x) return x;
        return bin[x] = find(bin[x]);
    }//查询
    void uni(int x, int y)
    {
        bin[find(x)] = find(y);
    }//合并
    bool check(int x, int y)
    {
        if(find(x) == find(y)) return true;
        return false;
    }//判断

    bool cmp(Edge x, Edge y)
    {
        return x.dis < y.dis;
    }

    void Kruskal()
    {
        for(int i = 1;i <= n;i++)
        bin[i] = i;
        sort(edge + 1, edge + 1 + m, cmp);
        //未建图情况下构建最小生成树
        /*for(int i = 1;i <= m;i++)
        {
            if(! check(edge[i].from, edge[i].to))
            {
                add(edge[i].from, edge[i].to, edge[i].dis);
                add(edge[i].to, edge[i].from, edge[i].dis);
                uni(edge[i].from, edge[i].to);
            }
        }*/
        for(int i = 1;i <= m;i++)
        {
            if(check(edge[i].to, edge[i].from))
            continue;
            ans += edge[i].dis;
            uni(edge[i].from, edge[i].to);
            if(++num_edge == n - 1)//树中边数 = 点数 - 1;
            break;
        }
    }
int main()
{
    scanf("%d %d", &n, &m);
    for(int i = 1;i <= m;i++)
    {
        int x, y, z;
        scanf("%d %d %d", &x, &y, &z);
        add(x, y, z);
    }
    Kruskal();
    printf("%d", ans);
    return 0;
}

? Kruskal使用贪心思想, 每次选取的边可以连接两不同集合且代价最小, 因此最后一定可以将n个点合并为一个集合并使总代价最小.

? 时间复杂度:O(m*logm), m为边数.

? 另外还有Prim算法也常用于求最小生成树,时间复杂度为O(n^2), 与Kruskal在实际使用时差距不大, 也可另作学习,

例题

luogu p1111 修复公路

题目背景

AAA地区在地震过后,连接所有村庄的公路都造成了损坏而无法通车。政府派人修复这些公路。

题目描述

给出A地区的村庄数n和公路数m,公路是双向的。并告诉你每条公路的连着哪两个村庄,并告诉你什么时候能修完这条公路。问最早什么时候任意两个村庄能够通车,即最早什么时候任意两条村庄都存在至少一条修复完成的道路(可以由多条公路连成一条道路)

输入格式

第1行两个正整数n, m

下面m行,每行3个正整数x, y, t,告诉你这条公路连着x, y两个村庄,在时间t时能修复完成这条公路。

输出格式

如果全部公路修复完毕仍然存在两个村庄无法通车,则输出?1,否则输出最早什么时候任意两个村庄能够通车。

思路

? 接近裸题, 直接根据题意求最小生成树, 唯一不同是因为题中需判断是否可以全连通, 所以在算法执行过程中直接统计全部边数, 最后根据边数进行判断.

代码

#include<bits/stdc++.h>

using namespace std;
    int n, m;
    int cnt;
    int num_edge;
    int bin[1000001];
    int ans;
    struct Edge
    {
        int from, to, dis;
    } edge[1000001];

    void add(int from, int to, int dis)
    {
        edge[++cnt].from = from;
        edge[cnt].to = to;
        edge[cnt].dis = dis;
    }

    int find(int x)
    {
        if(bin[x] == x) return x;
        return bin[x] = find(bin[x]);
    }
    void uni(int x, int y)
    {
        bin[find(x)] = find(y);
    }
    bool check(int x, int y)
    {
        if(find(x) == find(y)) return true;
        return false;
    }

    bool cmp(Edge a, Edge b)
    {
        return a.dis < b.dis;
    }

    void Kruskal()
    {
        sort(edge + 1, edge + 1 + m, cmp);
        for(int i = 1;i <= n;i++)
        bin[i] = i;
        for(int i = 1;i <= m;i++)
        {
            if(check(edge[i].from, edge[i].to)) continue;
            ans = max(ans, edge[i].dis);
            uni(edge[i].from, edge[i].to);
            //if(++num_edge == n - 1) break;
            num_edge++;
        }
    }
int main()
{
    scanf("%d %d", &n, &m);
    for(int i = 1;i <= m;i++)
    {
        int u, v, w;
        scanf("%d %d %d", &u, &v, &w);
        add(u, v, w);
    }
    Kruskal();
    if(num_edge >= n - 1)
    {
        printf("%d", ans);
        return 0;
    }
    printf("-1");
    return 0;
}

luogu p1194 买礼物

题目描述

又到了一年一度的明明生日了,明明想要买B样东西,巧的是,这B样东西价格都是A元。

但是,商店老板说最近有促销活动,也就是:

如果你买了第I样东西,再买第J样,那么就可以只花K{I,J}元,更巧的是, K{I,J}竟然等于K{J,I}。

现在明明想知道,他最少要花多少钱。

输入格式

第一行两个整数,A,B

接下来B行,每行B个数,第I行第J个为K{I,J}。

我们保证K{I,J}=K{J,I}, 并且K{I,I}=0。

特别的,如果K{I,J} = 0,那么表示这两样东西之间不会导致优惠。

输出格式

一个整数,为最小要花的钱数。

思路

? 直接最小生成树, 但因为两件物品先买哪一件优惠相同, 因此对于输入格式, 需判断i与j的大小关系以及w是否为0, 在进行Kruska前先将买第一件物品时由0到每一件物品建边, 代价为a.

代码

#include<bits/stdc++.h>

using namespace std;
    int a, b;
    int cnt;
    int ans;
    struct Edge
    {
        int from, to;
        int dis;
    } edge[1000001];
    int bin[1000001];
    int num_edge;
    void add(int from, int to, int dis)
    {
        edge[++cnt].from = from;
        edge[cnt].to = to;
        edge[cnt].dis = dis;
    }

    int find(int x)
    {
        if(x == bin[x]) return x;
        return bin[x] = find(bin[x]);
    }
    void uni(int x, int y)
    {
        bin[find(x)] = find(y);
    }
    bool check(int x, int y)
    {
        if(find(x) == find(y)) return true;
        return false;
    }

    bool cmp(Edge a, Edge b)
    {
        return a.dis < b.dis;
    }

    void Kruskal()
    {
        for(int i = 1;i <= b;i++)
        bin[i] = i;
        sort(edge + 1, edge + 1 + cnt, cmp);
        for(int i = 1;i <= cnt;i++)
        {
            if(check(edge[i].from, edge[i].to)) continue;
            ans += edge[i].dis;
            uni(edge[i].from, edge[i].to);
            if(++num_edge == b) break;
        }
    }
int main()
{
    scanf("%d %d", &a, &b);
    for(int i = 1;i <= b;i++)
    {
        for(int j = 1;j <= b;j++)
        {
            int w;
            scanf("%d", &w);
            if(i < j && w)
            add(i, j, w);
        }
    }
    for(int i = 1;i <= b;i++) add(0, i, a);
    Kruskal();
    printf("%d", ans);
    return 0;
}

END

图论 最小生成树

标签:最小   等于   格式   集合   思路   mst   连接   std   name   

原文地址:https://www.cnblogs.com/keliven/p/11296030.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!