草庐IT

最小生成树

睡不醒的汤姆 2023-10-31 原文

最小生成树

一、简介

最小生成树就是将 n 个顶点, n - 1 条边,通过一个连接起来,且使权值最小的一种结构。

换句话来说,就是给定一个无向图,在图中选择若干条边把图中的所有节点连接起来,要求边长之和最小。在图论中,叫做求最小生成树。

主要的应用:
比如要在 n 个城市之间铺设光缆,使得这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,所以我们的目标要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。

主要算法有 Prim算法Kruskal算法

下面通过一道Acwing上的一道最小生成树例题来分别简单介绍这两种算法。



二、Prim算法

可理解为 “加点法”, 每次迭代找到不在连通块中的距离最近的点,加入到连通块中,将连通块逐渐扩大,最后将整个图连通起来,并且边长之和最小。

1、先把所有点之间的距离初始化为正无穷

2、n次迭代,找到不在集合当中的最小的点,这个集合指当前已经在连通块中的所有点,找到该点赋给 t ,用 t 更新其他点到集合的距离,再把 t 加到集合当中去。

3、AC代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;

int n, m;//n 个点,m 条边
int g[N][N];//存储图
int dist[N];//存储各个点到生成树的距离
bool st[N];//判断点是否被加入到生成树中

//prim算法核心代码
int prim()
{
    //初始化距离数组为一个很大的数(10亿左右)
    memset(dist, 0x3f, sizeof(dist));
    
    int res = 0;//最小生成树所有边的长度之和
    
    //每次循环选出一个点加入到生成树
    for(int i = 0; i < n; i++){
    	//初始化为-1,表示没有找到任何一个点
        int t = -1;

        for(int j = 1; j <= n; j++){
        	//如果没有在树中,且到树的距离最短,则选择该点
            if(!st[j] && (t == -1 || dist[j] < dist[t]))
                t = j;//将该点赋给t
        }
        
        //一定要先累加,再进行更新生成树
        //遍历所有点,找不到连通的其他点,即不存在最小生成树
        if (i && dist[t] == INF)
            return INF;
            
        if (i)//把找到的符合条件的点的长度加上
            res += dist[t];
        
        //更新生成树,其他的点到生成树的最短距离
        for (int j = 1; j <= n; j++)
            dist[j] = min(dist[j], g[t][j]);
        
        //加入生成树中,标记
        st[t] = true;
    }
    return res;
}

int main()
{
    //各个点之间的距离初始化成无穷大
    memset(g, 0x3f, sizeof(g));
    
    cin >> n >> m;
    while(m --){
        int a, b, w;
        cin >> a >> b >> w;

		//存储两点之间最小的权重
        g[a][b] = g[b][a] = min(g[a][b], w);
    }

    int t = prim();
    
    //如果t为无穷大,则代表找不到符合条件的点,无法构成最小生成树
    if (t == INF)
        puts("impossible");
    else 
        printf("%d\n", t);
    
    return 0;
}

三、Kruskal算法

可理解为 “加边法”,最初最小生成树的边数为 0,每次迭代选择一条不在集合内的权值最短的边,加入到集合中,组成最小生成树。

1、使用快排将所有边按权值从小到大排序。时间复杂度为 O(log n).

2、枚举每组边 a 、b,权重 c ,如果 a、b不连通,就将这条边加入集合中,直到具有 n 个顶点的连通块筛选出来 n-1 条边为止。时间复杂度为 O(n) .

3、判断 a、b是否连通的方法为:使用并查集。

  • 初始化各个顶点在不同的集合中,父节点为它自己。
  • 按快排的从小到大的顺序遍历每条边,判断这条边的两个顶点是否有相同的父节点,如果有那就使在同一个集合中。
  • 如果该条边上的两个顶点在一个集合中,说明两个顶点已经连通,这条边不要。如果不在一个集合中,则加入这条边到集合中,连通这两个顶点。

总的时间复杂度为 O(n log n).

4、AC代码如下:

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 200010;

int n, m;
int p[N];

//结构体存储 两点及其权值
struct Edge
{
    int a, b, w;
    
    //重载小于号,因为再给边排序的时候是按照边的权重进行排序的,这样当两个边进行比较的时候就会使用他们的权重进行比较了
    bool operator < (const Edge &W)const
    {
        return w < W.w;
    }
}edges[N];

//查找根节点
int find(int x){
    if (p[x] != x)
        p[x] = find(p[x]);
    return p[x];
}

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};
    }
    
    //快排,将所有边的权值从小到大排序
    sort(edges, edges + m);
    
    //初始化并查集
    for (int i = 1; i <= n; i++)
        p[i] = i;
    
    后面这部分就是"Kruskal算法"的核心代码
    int res = 0;//res存的是最小生成树的所有边的权值
    int cnt = 0;//cnt存的是当前加入的边数
    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;//将 a,b 所在的两个集合连接起来
            res += w;//集合中的总权重加上这条边的权重
            cnt++;//因为加入的是a-b这一条边,将a,b所在的两个集合连接之后,总的边数+1
        }
    }
    
    
    //只有当 cnt == n - 1 时才能表示已经将所有点加入到集合中,可以生成最小生成树
    if (cnt < n - 1)
        puts("impossible");
    else 
        printf("%d\n", res);
    
    return 0;
}

四、小结

1、点多边少,即稀疏图,一般选用 Prim算法, 采用 邻接矩阵 存储边之间的关系。
2、点少边多,即稠密图,一般选用 Kruskal算法,采用 邻接表 存储边之间的关系,但更简单更常用的是用 结构体 的方式来存储。

有关最小生成树的更多相关文章

  1. ruby - 获取数组中的值并最小化某个类属性的最优雅的方法是什么? - 2

    假设我有以下类(class):classPersondefinitialize(name,age)@name=name@age=ageenddefget_agereturn@ageendend我有一组Person对象。是否有一种简洁的、类似于Ruby的方法来获取最小(或最大)年龄的人?如何根据它对它们进行排序? 最佳答案 这样做会:people_array.min_by(&:get_age)people_array.max_by(&:get_age)people_array.sort_by(&:get_age)

  2. ruby - 返回空白页的最小 Capybara/Poltergeist 测试 - 2

    看来我正在回顾SO帖子中采取的步骤:Capybara,PoltergeistandPhantomjsandgivinganemptyresponseinbody.(如果你愿意,可以将其标记为重复,但我包含了一个最小的独立测试用例和版本号。)问题我做错了什么吗?我可以运行另一个可能有助于隔离问题的最小测试吗?文件:pgtest.rbrequire'rubygems'require'capybara'require'capybara/dsl'require'capybara/poltergeist'modulePGTestincludeCapybara::DSLextendselfdeft

  3. ruby - 寻找产品和商店的最佳组合以最小化成本的算法 - 2

    你好,Stackoverflow的人们,我经营一个网站,为用户寻找最便宜的书籍购买地点。这对于单本书来说很容易,但对于多本书来说,有时在一家商店购买一本书而在另一家商店购买另一本书会更便宜。目前我找到了销售用户列表中所有书籍的最便宜的商店,但我想要一个更智能的系统。这里有更多信息:一本书的价格对于一家商店来说是不变的。运费可能会有所不同,具体取决于书籍的数量或书籍的总值(value)。每个商店对象都可以获取一组书籍并返回运费。通常,并非每家书店都出售每一本书。不确定在这里链接到我的站点是否很酷,但它列在我的用户配置文件中。我希望能够找到最便宜的商店和书籍组合。我担心这需要一种蛮力方法-

  4. 用于进行线性或非线性最小二乘近似的 Ruby 库? - 2

    是否有Ruby库允许我对一组数据进行线性或非线性最小二乘法逼近。我想做的是:给定一系列[x,y]数据点针对该数据生成线性或非线性最小二乘法近似值库不必弄清楚它是否需要进行线性或非线性近似。库的调用者应该知道他们需要什么类型的回归我不想尝试移植某些C/C++/Java库来获得此功能,因此我希望有一些现有的Ruby库可供我使用。 最佳答案 尝试使用“statsample”gem。您可以使用下面提供的示例执行对数、指数、幂或任何其他转换。我希望这有帮助。require'statsample'#IndependentVariablex_da

  5. ruby-on-rails - 如何在 Rails 3 的表格列中找到最小值 - 2

    嗨,假设我有一个table(ads),其中有一个column(views)观看次数21463如何找到该列中的最小值?有什么简单的方法可以做到这一点?这就是我的@ads=Ad.all@show_this_ad=@ads.min(:views)这给了我一个“参数数量错误(1代表0)错误”@ads=Ad.all@show_this_ad=@ads.minimum(:views)这给了我一个“未定义的方法错误” 最佳答案 Ad.minimum(:views)应该可以您仍然可以添加更多限制,例如:Ad.where(:user_id=>1234

  6. ruby - 在 Ruby 中获取整数数组的最小公倍数 - 2

    我知道,在Ruby中,您可以使用Integer#lcm求两个数的最小公倍数的方法。例如:10.lcm(15)#=>30是否有一种有效的(或内置于核心或标准库中)方法来获取给定数组中所有整数的最小公倍数?例如:[5,3,10,2,20].lcm#=>60 最佳答案 任何需要两个操作数的操作都可以通过folding迭代地应用于一个集合它:Enumerable#inject/reduce.为了覆盖空情况,将identityelement作为第一个参数传递操作的最小公分母为1。[5,3,10,2,20].reduce(1){|acc,n|a

  7. Ruby:如何找到最小数组元素的索引? - 2

    有什么办法可以更优雅地重写这个吗?我认为,这是一段糟糕的代码,应该重构。>>a=[2,4,10,1,13]=>[2,4,10,1,13]>>index_of_minimal_value_in_array=a.index(a.min)=>3 最佳答案 我相信这只会遍历数组一次并且仍然很容易阅读:numbers=[20,30,40,50,10]#=>[20,30,40,50,10]elem,idx=numbers.each_with_index.min#=>[10,4] 关于Ruby:如何找

  8. ruby - 如何使用 Ruby 找到最小值/最大值 - 2

    我想使用min(5,10)或Math.max(4,7)。Ruby中有实现这种效果的函数吗? 最佳答案 你可以做到[5,10].min或[4,7].max它们来自Enumerablemodule,因此任何包含Enumerable的内容都将具有可用的方法。v2.4引入了自己的Array#min和Array#max,它们比Enumerable的方法快得多,因为它们跳过调用#each.@nicholasklick提到了另一个选项,Enumerable#minmax,但这次返回一个[min,max]数组。[4,5,7,10].minmax=>

  9. javascript - 与用户操作交互的模态弹出窗口,如用户设置的最大化、最小化、关闭、调整大小和可拖动 - 2

    我需要与用户操作交互的模态弹出窗口,如下图所示。但是这个模态弹出窗口应该是纯java脚本。严禁使用JQuery或JQuery插件。期待您的来信。提前致谢。 最佳答案 这里我分享一些插件,基本上都是用Jquery和Javascript创建的。无论您在纯JavaScript中寻找什么,都可以使用http://alpha.jspanel.de/media/demos/nojquery/index.php另一个是使用Jquery创建的。是https://lobianijs.com/site/lobipanel#examples使用第一个选项

  10. 使用 Genie 效果最小化 <div> 的 Javascript? - 2

    这个问题不太可能帮助任何future的访问者;它只与一个小的地理区域、一个特定的时间点或一个非常狭窄的情况有关,这些情况并不普遍适用于互联网的全局受众。为了帮助使这个问题更广泛地适用,visitthehelpcenter.关闭10年前。我想最小化一个框,就像Mac上Sprite效果中的弹出窗口一样,我找到了jQueryTransfereffect很接近,但还不够,它只画了一个轮廓,并没有真正涂抹物体,有没有办法模仿Mac的Sprite最小化效果?谢谢。

随机推荐