• 最小生成树


    一、定义

    1、生成树

    在一个无向连通图中,如果存在一个连通子图包含原图中的所有结点和部分边,且这个子图不存在回路,那么该子图被称为原图的一棵生成树。

    2、最小生成树

    所有生成树中,边权和最小的那一棵(或那几棵)叫做最小生成树(MST)。

     

    二、构造算法

    有两种算法来构造最小生成树:普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法。

    三、普里姆(Prim)算法

    算法步骤:

    1、在图G=(V, E)(V表示顶点,E表示边)中,从集合V中任取一个顶点(起始点)放入集合 U中,这时 U={v0},集合T(E)为空。

    2、从v0出发寻找与U中顶点相邻(另一顶点在V中)权值最小的边的另一顶点v1,并使v1加入U。即U={v0,v1 },同时将该边加入集合T(E)中。

    3、重复2,直到U=V为止。

    这时T(E)中有n-1条边,T = (U, T(E))就是一棵最小生成树。

    构造过程:

    算法模板(hdoj1233):

     1 /**
     2  * 假设图中有n个结点,m条边(代码中m=n*(n-1)/2),使用邻接矩阵map[][]存储图,
     3  * 求图中最小生成树的边权值。具体输入输出要求参见hdoj1233。
     4  */
     5 #include <algorithm>
     6 #include <cstring>
     7 #include <cstdio>
     8 using namespace std;
     9 
    10 const int INF = 0x7fffffff;
    11 const int N = 100 + 10;
    12 int map[N][N];
    13 int dist[N];   
    14 int n;
    15 
    16 void prim()
    17 {
    18     int min_edge, min_node;
    19     for (int i = 1;i <= n;i++)
    20         dist[i] = INF;
    21     int ans = 0;
    22     int now = 1;
    23     for (int i = 1;i < n;i++)
    24     {
    25         dist[now] = -1;
    26         min_edge = INF;
    27         for (int j = 1;j <= n;j++)
    28         {
    29             if (j != now && dist[j] >= 0)
    30             {
    31                 if (map[now][j]>0)
    32                     dist[j] = min(dist[j], map[now][j]);
    33                 if (dist[j] < min_edge)
    34                 {
    35                     min_edge = dist[j];    //min_edge存储与当前结点相连的最短的边
    36                     min_node = j;
    37                 }
    38             }
    39         }
    40         ans += min_edge;    //ans存储最小生成树的长度
    41         now = min_node;
    42     }
    43     printf("%d
    ", ans);
    44 }
    45 
    46 int main()
    47 {
    48     while (scanf("%d", &n) == 1 && n)
    49     {
    50         memset(map, 0, sizeof(map));
    51         int a, b, c;
    52         int nums = n*(n - 1) / 2;
    53         for (int i = 0; i < nums; i++)
    54         {
    55             scanf("%d%d%d", &a, &b, &c);
    56             map[a][b] = c;
    57             map[b][a] = c;
    58         }
    59         prim();
    60     }
    61     return 0;
    62 }

    prim算法涉及到两个集合V和U,V包含了图中所有结点,U则包含了当前最小生成树中的结点。上面代码中的数组dist[]存储了从集合U中的结点到集合V-U的结点的最短距离,如果编号为i的结点已经在U中了,则令dist[i]=-1。每次从V-U中选取结点添加到U时,则选取最小dist[]值对应的那个结点,dist[]在循环过程中是不断更新的。

    三、克鲁斯卡尔(Kruskal)算法

     算法步骤:

    1、初始时所有结点属于孤立的集合;

    2、按照边权递增顺序遍历所有的边,若遍历到边的两个定点属于不同的集合(该边即为连通这两个集合的边中权值最小的那条),则确定该边为最小生成树上的一条边,并将这条边的两个顶点所属的集合合并;

    3、遍历完所有边后,若原图上所有结点属于同一个集合,则原图结点和被选中的边构成最小生成树;否则原图不连通,最小生成树不存在。

    构造过程:

     

    算法模板(hdoj1233):

     1 /**
     2  * 假设图中有n个结点,m(代码中m=n*(n-1)/2)条边,使用邻接矩阵map[][]存储图,
     3  * 求图中最小生成树的边权值。具体输入输出要求参见hdoj1233。
     4  */
     5 #include <algorithm>
     6 #include <cstring>
     7 #include <cstdio>
     8 #include <vector>
     9 using namespace std;
    10 
    11 struct Edge
    12 {
    13     int a, b, dist;
    14 
    15     Edge() {}
    16     Edge(int a, int b, int d) :a(a), b(b), dist(d) {}
    17     bool operator < (Edge edge)    //按边长从短到长排序
    18     {
    19         return dist < edge.dist;
    20     }
    21 };
    22 
    23 const int N = 100 + 10;
    24 int p[N];    //并查集使用
    25 vector<Edge> v;
    26 int n;
    27 
    28 int find_root(int x)
    29 {
    30     if (p[x] == -1)
    31         return x;
    32     else return find_root(p[x]);
    33 }
    34 
    35 void kruskal()
    36 {
    37     memset(p, -1, sizeof(p));
    38     sort(v.begin(), v.end());
    39     int ans = 0;
    40     for (int i = 0; i < v.size(); i++)
    41     {
    42         int ra = find_root(v[i].a);
    43         int rb = find_root(v[i].b);
    44         if (ra != rb)
    45         {
    46             ans += v[i].dist;
    47             p[ra] = rb;
    48         }
    49     }
    50     printf("%d
    ", ans);
    51 }
    52 
    53 int main()
    54 {
    55     while (scanf("%d", &n) == 1 && n)
    56     {
    57         int a, b, d;
    58         int nums = n*(n - 1) / 2;
    59         v.clear();
    60         for (int i = 0; i < nums; i++)
    61         {
    62             scanf("%d%d%d", &a, &b, &d);
    63             v.push_back(Edge(a, b, d));
    64         }
    65         kruskal();
    66     }
    67 }
  • 相关阅读:
    【转载】关于Java String, StringBuilder, StringBuffer, Hashtable, HashMap的面试题
    LeetCode: 【L4】N-Queens 解题报告
    【转载】在美国找工作秘籍
    Lintcode: Kth Largest Element 解题报告
    LeetCode: Reverse Integer 解题报告
    Lintcode: First Bad Version 解题报告
    九章面试题:Find first K frequency numbers 解题报告
    tomcat之虚拟目录
    百度搜索结果如何屏蔽百家号内容
    CentOS7之Rsync+Inotify架构实现实时同步文件和文件夹
  • 原文地址:https://www.cnblogs.com/sench/p/7967746.html
Copyright © 2020-2023  润新知