图论_最小生成树(更新中)
[最小生成树]
[前导知识]:
1.连通图:对于无向图来说,任意两点之间都有路径连接的图叫做连通图。
2.树:没有圈的连通图称为树。(废话大师)
3.生成树(Spanning Tree):对于连通图来说,生成树必须满足以下2个条件
(1)包含连通图中所有的顶点;
(2)任意两顶点之间有且仅有一条通路;
推论:连通图的生成树具有这样的特征,即生成树中 e = v - 1;(其中e为边数,v为顶点数)
(注意:连通图中,由于任意两顶点之间可能含有多条通路,遍历连通图的方式有多种,往往一张连通图可能有多种不同的生成树与之对应)
[不相关小知识]:(搞人心态是吧,还弄个不相关小知识,不相关你别写啊)
1.生成森林:对于非连通图(可分解为多个连通分量,每个连通分量至少对应一棵生成树)来说,多个生成树称为生成森林。
[定义]:如果一棵生成树有边权值,那么使得边权值和最小的生成树称为最小生成树(MST,Minimum Spannning Tree)
[相关算法]:
1.Prim算法
(1)简述:Prim算法和Dijkstra算法十分相似,都是从某个顶点出发,不断添加边的算法。
(2)分析:
首先,我们假设一棵只包含一个顶点v的树T。然后贪心地选取T和其他顶点之间相连的最小权值的边,并把它加到T中。
不断进行这个操作,就可以得到一棵树了。接下来我们来证明通过这个方法得到的生成树就是最小生成树。
(注:这个顶点v可以任意选,不影响贪心结果,证明本蒻蒟不会)
//书本证明:
我们令V表示顶点的集合。假设现在已经求得的生成树的顶点的集合是X (X 包含于 V),并且存在在V上的最小生成树使得T是它的一个子图。
下面我们证明存在一棵最小生成树使得T是它的一个子图并且它包含了连接X和V\X 之间的边中权值最小的边。记连接X和V\X 的权值最小
的边为e,它连接着v(∈ X) 和 u(∈ V\X)。根据假设,存在一棵V上的最小生成树使得T是它的一个子图。如果e也在这棵最小生成树上,问题
就得到证明了,所以我们假设e不在这棵树上。因为生成树本质是一棵树,所以在添加了e之后就产生了圈。圈上的边中,必然存在一条和e
不同的边f连接着X和V\X。从e的定义可以知道f的权值不超过原来的生成树。因此可以说存在同时包含e和T的最小生成树。所以把e加入T中满足
最初的假设。可以这样不断地加入新的边,直到X = V。因为存在V上的最小生成树使得T是它的一个子图,而X = V,所以T就是V上最小的生成树。
(注:X表示X顶点集合, V\X表示在V集合中除外X的顶点集合)
如何查找最小权值的边?
把X和顶点V连接的边的最小权值记为mincost[v]。在向X里添加顶点u时,只需要查看和u相连的边就可以了。对于每条边,
更新mincost[v] = min(mincost[v], 边(u,v)的权值)即可。
如果每次都遍历未包含在X中的点的mincost[v],需要O(|V|*|V|)时间。不过和Dijkstra算法一样,如果使用堆来维护mincost
时间复杂度就是O(|E|log|V|)。
[prim算法 代码实现]
1 int cost[maxn][maxn];//cost[u][v] 表示边e = (u,v)的权值(不存在时设置为INF) 2 int mincost[maxn];//从集合X出发到各个顶点的最小权值 3 bool used[maxn];//顶点i是否包含在集合X中 4 int V;//顶点数 5 6 int prim() 7 { 8 //初始化 9 for (int i = 0; i < V; ++i)//编号 0~V - 1 10 { 11 mincost[i] = INF; 12 used[i] = false; 13 } 14 mincost[0] = 0; 15 int res = 0;//最小生成树的大小 16 17 //更新 18 while (true) 19 { 20 int v = -1;//从不属于X的顶点中选取从X到其权值最小的顶点 21 for (int u = 0; u < V; ++u) 22 if (!used[u] && (v == -1 || mincost[u] < mincost[v])) 23 v = u; 24 25 if (v == -1) 26 break; 27 used[v] = true;//把顶点v加入X 28 res += mincost[v];//把边的长度加入结果中 29 for (int u = 0; u < V; ++u) 30 mincost[u] = min(mincost[u], cost[v][u]); 31 } 32 return res; 33 }
[进阶]:堆优化
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int maxn = 4e5 + 10; 4 typedef pair<int, int> pii; 5 vector<pii> vec[maxn]; 6 int vis[maxn];//记录顶点i是否已经编入树 7 int main() 8 { 9 int n, m; 10 int u, v, val; 11 int ans = 0; 12 scanf("%d%d", &n, &m); 13 memset(vis, 0, sizeof(vis)); 14 for (int i = 1;i <= m;++i) { 15 scanf("%d%d%d", &u, &v, &val);//读入边 16 vec[u].push_back(pii(val, v));//顶点u到v距离为val 17 vec[v].push_back(pii(val, u));//顶点v到u距离为val 18 } 19 priority_queue<pii, vector<pii>, greater<pii> > pq;//优先队列:小顶堆 20 vis[1] = 1;//记录1已编入树 21 for (int i = 0; i < (int)vec[1].size(); ++i) 22 pq.push(vec[1][i]);//将与1相连的顶点入队 23 while (!pq.empty()) 24 { 25 pii now = pq.top(); 26 pq.pop(); 27 if (!vis[now.second])//当前顶点没有被访问过 28 { 29 vis[now.second] = 1;//标记已经被访问过 30 ans += now.first;//将最短边添加入树(小顶堆已经帮我们排序好了,所以可以直接添加) 31 } 32 for (int i = 0;i < (int)vec[now.second].size();++i)//枚举与刚刚进树的这个顶点 33 { 34 if (!vis[vec[now.second][i].second])//相邻的顶点 35 pq.push(vec[now.second][i]);//将其进队列 优先队列会自动排序 36 } 37 } 38 bool flag = true; 39 for (int i = 1; i <= n; ++i)//检查是否所有点已经编入树,如果没有则代表图不连通无法构成最小生成树 40 if (!vis[i]) 41 { 42 flag = false; 43 break; 44 } 45 if (flag) 46 printf("%d\n", ans); 47 else 48 printf("orz\n"); 49 return 0; 50 }
2.Kruskal算法
Kruskal算法按照边的权值的顺序从小到大看一遍,如果不产生圈(重边等也算在内)
,就把当前这条边加入到生成树中。
如何判断是否产生圈?
假设现在要把连接顶点u和顶点v的边e加入生成树中。如果加入之前u和v不在同一个连通分量内
,那么加入e也不会产生圈。反之,如果u和v在同一个连通分量内,那么一定会产生圈。可以使用并查集
高效地判断是否属于同一个连通分量。
Kruskal算法在边的排序上最费时,算法的复杂度是O(|E|log|V|)。
[代码]
1 //并查集 模板 2 const int MAX_V = ???;//顶点数上限 3 int father[MAX_V]; 4 int find(int x) 5 { 6 if (x == father[x]) 7 return x; 8 return father[x] = find(father[x]); 9 } 10 void init_union_find(int v) 11 { 12 for (int i = 0; i < V; ++i) 13 father[i] = i; 14 } 15 bool same(int u, int v) 16 { 17 return find(u) == find(v); 18 } 19 void unite(int u, int v) 20 { 21 father[find(u)] = find(v); 22 } 23 // 24 struct edge 25 { 26 int u,v,cost; 27 }; 28 bool com(const edge& e1, const edge& e2) 29 { 30 return e1.cost < e2.cost; 31 } 32 const int MAX_E = ???;//边数上限 33 edge es[MAX_E]; 34 int V,E;//顶点数 边数 35 int kruskal() 36 { 37 sort(es, es + E, com);//按照edge.cost的顺序由小到大排序 38 //(注: sort函数中的比较函数不用填写括号) 39 init_union_find(V);//初始化并查集 40 int res = 0; 41 for (int i = 0; i < E; ++i) 42 { 43 edge e = es[i]; 44 if (!same(e.u,e.v)) 45 { 46 unite(e.u,e.v); 47 res += e.cost; 48 } 49 } 50 return res; 51 }

浙公网安备 33010602011771号