zoukankan      html  css  js  c++  java
  • 浅谈最小生成树

    浅谈最小生成树

                ———( m BiuBiu\_Miku)

    1.一些概念

      · :在一个中,满足边数等于点数减一的条件。(如图1所示)

      · 生成树:在一个连通图中,截取一个子图,此子图满足树的性质,且通过每一个节点的树称为生成树。(如图2所示)

      · 最小生成树:在一个包含 (n) 个节点的加权连通图中,所有边的边权之和最小的树,且通过每一个节点的树,即为最小生成树。(如图3所示)



    2.例题引入(题皆出自洛谷

    【模板】最小生成树

    题目描述

      给出一个无向图,求出最小生成树,如果该图不连通,则输出 (orz)

    输入格式

      第一行包含两个整数 (N,M),表示该图共有 (N) 个结点和 (M) 条无向边。
      接下来 (M) 行每行包含三个整数 (X_i,Y_i,Z_i) 表示有一条长度为 (Z_i) 的无向边连接结点 (X_i,Y_i)

    输出格式

       如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 (orz)

    输入输出样例

    输入

    4 5
    1 2 2
    1 3 2
    1 4 3
    2 3 4
    3 4 3
    

    输出

    7
    

    3.算法实现

       关于算法的实现一般有两种算法,第一种称为 (Kruskal) ,第二种称为 (Prim)

       实现方法一.(Kruskal) 时间复杂度 (O( MlogM ))(M)为边数)

         (Kruskal) 是一种利用并查集来实现最小生成树的办法,其算法流程大致为。

         先将读进来的边按照边权排序,然后利用并查集建立关系,一但发现如果两个点不存在关系,那么就建这条边,不然的话就不建,最后把建了边的边权统计起来就好了。

         为什么这样就可以找到最小生成树呢?因为我们已经将边权排序过了,也就是说越前面的边他的边权就越小,因此我们就可以直接通过简单的步骤得到最小生成树了!

         以上面的题目的样例来做例子,来模拟一下该算法全过程:

          1.排序,排序后得到以下的数据

    1 2 2
    1 3 2
    1 4 3
    3 4 3
    2 3 4
    

          2.发现 (1)(2) 不连通,于是建边,如下图所示:

          

          3.发现 (1)(3) 不连通,于是建边,如下图所示:

          

          4.发现 (1)(4) 不连通,于是建边,如下图所示:

          

          5.发现 (3)(4) 已经连通,于是不建边。

          6.发现 (2)(3) 已经联通,于是不建边。

          7.经统计,一共建的边的边权之和为 (7) 于是就输出 (7)

         (Code:)
    #include<bits/stdc++.h>
    using namespace std;
    int f[200005],m;
    struct edge{
    	int a,b,lo;
    }E[200005];	//边 
    int n,ans;
    bool cmp(edge x,edge y){return x.lo<y.lo;} //排序 
    int getfather(int k){ //并查集 
    	if(f[k]==k)return k;
    	return f[k]=getfather(f[k]);
    }
    int main(){
        scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++)f[i]=i;
    	for(int i=1;i<=m;i++)scanf("%d%d%d",&E[i].a,&E[i].b,&E[i].lo);
    	sort(E+1,E+1+m,cmp); //排序 
    	for(int i=1;i<=m;i++){
    		if(getfather(E[i].a)!=getfather(E[i].b)){	//判断两个点是否连通 
    			ans+=E[i].lo;	//若不连通建边 
    			f[getfather(E[i].a)]=getfather(E[i].b);
    			n--;	//减掉一个未连通点 
    		}
    		if(n==1){	//若全部连通就输出答案 
            	printf("%d
    ",ans);
    			return 0;
    		}
    	}
    	printf("orz
    ");	//否则输出orz 
    	return 0;
    } 
    

       实现方法二.(Prim) (时间复杂度根据不同情况,不同计算)

         (Prim) 是一种利用不断寻找最小值来实现找到最小生成树的方法。

         具体来说就是以某一个点为起点(一般选择节点 (1) 为起点),寻找能走的边中边权(代价)最小的边,再将此点收入囊中,也就是看做是一段子图。

         然后更新能走的边所花费的代价,因为当我加入一个节点后,该节点也可以通到别的点,而且从此新节点出发走原来可以走的边,可能存在更小代价.

        例如:若此时 (A) 已经与 (B) 已经连接,(A)(C) 代价为 (3)(B) 存在一条边到 (C) 的代价为 (1) ,那么此时就可以更新当前可以走的边。

         以上面的题目的样例来做例子,来模拟一下(Prim) 算法全过程:

          1.选择节点 (1) 为起点,并发现当前 (1)(2)(1)(3) 都是当前能走的边中边权最小的,于是任意走一条即可(这里选择走 (1)(2) ),如下图所示。

          

          2.当前能走的边中发现 (1)(3) 是边权最小的边,于是走此边,如下图所示。

          

          3.当前能走的边中,发现当前 (1)(4)(3)(4) 都是当前能走的边中边权最小的,于是任意走一条即可(这里选择走 (1)(4) ),如下图所示。

          

          4.找到最小生成树,经统计,走过的边权总和为 (7) 于是输出 (7)

         (Code:) (这里用邻接矩阵存图,复杂度为 (O(N^2)) N表示点的数量 )
    #include<bits/stdc++.h>
    using namespace std;
    int n,mmin=INT_MAX,dis[200005],ans,w[5005][5005],walk,m;	
    bool vis[200005];	//vis表示该边是否被访问过 
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=n;j++)
    			w[i][j]=1e9;		//矩阵初始化 
    	for(int i=1;i<=m;i++){
    		int a,b,c;
    		scanf("%d%d%d",&a,&b,&c);
    		w[a][b]=min(c,w[a][b]);
    		w[b][a]=min(c,w[b][a]); 	//邻接矩阵存图 
    	}
    	for(int i=1;i<=n;i++)dis[i]=w[1][i];	//初始化从起点开始能到达的边,能到达就赋值走到该点要付出的代价,否则为1e9 
    	dis[1]=0;
    	vis[1]=true;
    	for(int i=2;i<=n;i++){
    		mmin=1e9;				
    		for(int j=2;j<=n;j++)
    			if(dis[j]<mmin&&!vis[j]){	//找到当前所有能走的边的最小值 
    				mmin=dis[j];
    				walk=j;
    			}
    		ans+=mmin;			//走这条边 
    		vis[walk]=true;		//标记已经走过 
    		for(int j=2;j<=n;j++)if(!vis[j])dis[j]=min(dis[j],w[walk][j]); //更新能走的边花费的价值 
    	} 
    	printf("%d
    ",ans); 
    	return 0; 
    } 
    

    4.题目推荐

      [USACO05MAR]Out of Hay S:相当于模板(双倍经验,岂不美哉)
      [USACO3.1]最短网络 Agri-Net:本题用 (for) 建边后跑流程即可。
      公路修建:本题要运用两点之间的距离公式 (sqrt{( x_1 - x_2 )^2+( y_1 - y_2 )^2}) 来计算边权,然后跑流程就好了,本题推荐使用 (Prim) 来实现。

      (PS) :以上题目适合初学者食用。

    5.结语

      以上便是此博客的全部内容,其主要适用于没学过最小生成树的人用,有什么写得不好也欢迎大佬在评论区指出,感谢大佬的阅读。

    作者:BiuBiu_Miku

    -----------------------------------------------

    个性签名:天生我材必有用,千金散尽还复来!

    如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!

    万水千山总是情,打赏一分行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!

  • 相关阅读:
    Windows下如何通过PLSQL连接Oracle
    Windows下安装Oracle
    Linux下安装Oracle
    Linux命令学习-top命令
    Python3下字典、字符串及列表的相互转换
    Linux下搭建Jmeter+Ant+Jenkins自动化测试框架
    Linux下利用Ant调用Jmeter脚本生成HTML测试报告
    Linux下安装Ant
    Linux下安装Jmeter
    python连接数据库
  • 原文地址:https://www.cnblogs.com/BiuBiu-Miku/p/14132866.html
Copyright © 2011-2022 走看看