【算法总结】图论-最小生成树
一、概念
在一个无向连通图中,如果存在一个连通子图包含原图中所有的结点和部分边,且这个子图不存在回路,那么我们称这个子图为原图的一棵生成树。在带权图中,所有的生成树中边权的和最小的那棵(或几棵)被称为最小生成树。
定理:在要求解的连通图中,任意选择一些点属于集合A,剩余的点属于集合B, 必定存在一棵最小生成树包含两个顶点分别属于集合 A 和集合 B 的边(即连通两个集合的边)中权值最小的边。
二、Kruskal算法
1.初始时所有结点属于孤立的集合。
2.按照边权递增顺序遍历所有的边,若遍历到的边两个顶点仍分属不同的集合(该边即为连通这两个集合的边中权值最小的那条)则确定该边为最小生成树上的一条边,并将这两个顶点分属的集合合并。
3.遍历完所有边后,原图上所有结点属于同一个集合则被选取的边和原图中所有结点构成最小生成树;否则原图不连通,最小生成树不存在。
如步骤所示,在用Kruskal算法求解最小生成树的过程中涉及到大量的集合操作,我们恰好可以使用上一节中讨论的并查集来实现这些操作。
例 5.3 还是畅通工程
解题思路
在给定的道路中选取一些,使所有的城市直接或间接连通且使道路的总长度最小,该例即为典型的最小生成树问题。我们将城市抽象成图上的结点,将道路抽象成连接点的边,其长度即为边的权值。经过这样的抽象,我们求得该图的最小生成树,其上所有的边权和即为所求。 注意城市编号是从1开始的,由于p[i]=i,所以遍历也要从1开始。
AC代码
#include<cstdio> #include<algorithm> using namespace std; const int N = 101; int Tree[N]; int findRoot(int x) { if (Tree[x] == -1)return x; else { int tmp = findRoot(Tree[x]); Tree[x] = tmp; return tmp; } } struct Edge//边结构体 { int a, b;//边两个顶点的编号 int cost;//边的权值 bool operator <(const Edge &A)const//重载小于号使得可以按照边权从小到大排列 { return cost < A.cost; } }edge[6000]; int main() { int n; while (scanf("%d", &n) != EOF && n != 0) { for (int i = 1; i <= n * (n - 1) / 2; i++) scanf("%d%d%d", &edge[i].a, &edge[i].b, &edge[i].cost); sort(edge + 1, edge + 1 + n * (n - 1) / 2);//起始元素为edge[1],一共n * (n - 1) / 2个待排序元素 for (int i = 1; i <= n; i++) Tree[i] = -1;//所有结点为孤立集合 int ans = 0;//最小生成树上边权和初始化为0 for (int i = 1; i <= n * (n - 1) / 2; i++) { int a = findRoot(edge[i].a); int b = findRoot(edge[i].b);//查找边的两个顶点所在集合的信息 if (a != b) {//若他们属于不同集合,则选用该边 Tree[a] = b;//合并集合 ans += edge[i].cost;//累加权值 } } printf("%d\n", ans); } return 0; }