diff --git a/algorithm/graph/MST.md b/algorithm/graph/MST.md index 1e8ece6..db82e9b 100644 --- a/algorithm/graph/MST.md +++ b/algorithm/graph/MST.md @@ -1,19 +1,41 @@ # 最小生成树 +最小生成树(minimum spanning tree, MST)是连通加权无向图中一棵权值最小的生成树。无向图的生成树是具有图的全部顶点,但边数最少的连通子图。 + +练习题: [POJ 1258:Agri-Net](http://bailian.openjudge.cn/practice/1258/) ## 1. Prim算法 ### 1.1 算法描述 -TODO +Prim算法又称"加点法", 从只包含一个顶点的生成树开始, Prim算法每次贪心地选择一个离当前生成树最近的点加入到生成树, 直到加入连通图的所有顶点。 + +算法步骤: + +1. 输入:加权连通图G,其中顶点集合为V,边集合为E; + +2. 初始化: Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {} 为空; + +3. 重复下列操作,直到Vnew = V: + a.在集合E中选取权值最小的边,其中u为集合Vnew中的元素,而v不在Vnew集合当中; + b.将v加入集合Vnew中,将边加入集合Enew中; + +4. 输出:Vnew 和 Enew 即所得到的最小生成树。 + +实现时,我们可以用一个数组`processd`来记录某个节点是否已经位于集合Vnew中。另外,常见题目都是求最小生成树的代价,所以我们只需再用一个数组`lowCost`来记录那些横跨当前生成树内外的最短边长度,每次加入一个点后都要适当更新`lowCost`。 + +时间复杂度: +* 邻接矩阵 O(V^2) +* 邻接表 O(ElogV) ### 1.2 代码 ``` C++ +const int INF = 0x7FFFFFFF; int Prim(vector >&G, int start){ /* - 用Prim算法求最小生成树的代价. + 用Prim算法求最小生成树的代价. (与Dijkstra算法很相似, 注意对比) Prim算法: - 又称"加点法", 每次选择横跨最小生成树内外的边中代价最小的边对应的点, 加入到最小生成树中。 + 又称"加点法", 每次选择横跨当前生成树内外的边中代价最小的边对应的点, 加入到最小生成树中。 参数: - G: 连通图的邻接矩阵 + G: 连通图的邻接矩阵, 权值为0x7FFFFFFF代表无边 start: 起始结点编号 时间复杂度O(v^2) @@ -30,9 +52,9 @@ int Prim(vector >&G, int start){ // 不断加入剩下的 n-1 个结点到生成树结点集合中 for(int i = 1; i < n; i++){ // 循环 n-1 次 - int mincost = 0x7FFFFFFF, v = -1; + int mincost = INF, v = -1; for(int j = 0; j < n; j++){ - if(!processd[j] && mincost >= lowCost[j]){ + if(!processd[j] && mincost > lowCost[j]){ mincost = lowCost[j]; v = j; } @@ -43,7 +65,8 @@ int Prim(vector >&G, int start){ } // 将结点v加入到已处理集合中 processd[v] = 1; res += mincost; - // 更新每个结点到生成树结点集合的最短边长度 + + // 更新剩余结点到生成树结点集合的最短边长度 for(int j = 0; j < n; j++) if(!processd[j] && G[v][j] < lowCost[j]) lowCost[j] = G[v][j]; @@ -53,4 +76,74 @@ int Prim(vector >&G, int start){ ``` ## 2. Kruskal算法 -TODO \ No newline at end of file + +### 2.1 算法描述 + +Kruskal算法把所有的边按照权值先从小到大排列,接着按照顺序选取每条边,如果这条边的两个端点不属于同一集合,那么就将它们合并,直到所有的点都属于同一个集合为止。 + +算法步骤: +1. 输入:加权连通图G; +2. 新建图Gnew, 包含G的所有顶点但是无边; +3. 从权值最小的边开始,如果这条边连接的两个节点在Gnew中不在同一个连通分量中,则添加这条边到图Gnew中; +4. 重复3,直至Gnew中所有的节点都在同一个连通分量中。 + +算法涉及到集合的查询与合并,所以可以使用并查集。 + +时间复杂度: O(ElogV) + +### 2.2 代码 + +``` C++ +vectormake_set(int n){ + /*并查集建立*/ + vectorfather(n); + for(int i = 0; i < n; i++) father[i] = i; + return father; +} +int find_set(vector&father, int x){ + /*查询, 带路径压缩*/ + if(father[x] != x) + father[x] = find_set(father, father[x]); + return father[x]; +} +int union_set(vector&father, int x, int y){ + /*合并, 这里并没有用到这个函数*/ + int xf = find_set(father, x); + int yf = find_set(father, y); + if(xf != yf) father[xf] = yf; +} + + +struct Edge{ // 边定义 + int from, to, len; // from和to从0开始 + Edge(int f, int t, int l){from=f; to=t; len=l;} + bool operator < (const Edge &e) const{ + return len < e.len; + } +}; +int Kruskal(vector&edges, int n){ + /* + 用Kruskal算法求最小生成树的代价. + Kruskal算法: + 从小到大选取每条边,如果该边的两个端点不属于同一集合,那么就将它们合并, + 直到所有的点都属于同一个集合为止。 + 参数: + edges: 存放所有边的数组 + n : 图节点总数 + */ + int res = 0; + vectorsets = make_set(n); // 建立并查集 + sort(edges.begin(), edges.end()); // 所有边排序 + // 遍历每一条边 + for (int i = 0; i < edges.size(); ++i) { + Edge e = edges[i]; + int x = find_set(sets, e.from); + int y = find_set(sets, e.to); + if (x != y){ // 属于不同连通子图, 加入生成树 + res += e.len; + sets[x] = y; // 合并 + } + } + return res; +} +``` \ No newline at end of file