最小生成树(C#)(算法第四版)
2020-04-08 21:58
19 查看
最小生成树 - 加权无向图(算法第四版)
约定
这里只简单的介绍一般情况下的最小生成树问题,因此最加权图数据做了一些规定:
- 只考虑连通图(如果不满足会产生最小森林)
- 所有边的权重不同(不满足会出现多个最小生成树)
加权图数据结构
using System; using System.Collections.Generic; using System.Text; namespace 图 { public class EdgeWeightedGraph { private List<Edge>[] _adj; //邻接表 private int _v; //结点数 private int _e; //边数 /// <summary> /// 按文件数据生成一个加权无向图, /// </summary> /// <param name="path">图文件路径</param> public EdgeWeightedGraph(string path) { string line; System.IO.StreamReader sr = new System.IO.StreamReader(path); _v = Convert.ToInt32(sr.ReadLine()); _e = 0; _adj = new List<Edge>[_v]; for (int i = 0; i < _v; i++) { _adj[i] = new List<Edge>(); } while ((line = sr.ReadLine()) != null) { string[] str = line.Split(' '); int a = Convert.ToInt32(str[0]); int b = Convert.ToInt32(str[1]); float w = (float)Convert.ToDouble(str[2]); AddEdge(new Edge(a,b,w)); } } public int V => _v; public int E => _e; public EdgeWeightedGraph(int v) { this._v = v; _adj = new List<Edge>[v]; } public void AddEdge(Edge e) { int v = e.Either, w = e.Other(v); _adj[v].Add(e); _adj[w].Add(e); _e++; } public Edge[] Adj(int v) { return _adj[v].ToArray(); } public Edge[] Edges() { List<Edge> edges = new List<Edge>(); for (int i = 0; i < _adj.Length; i++) { for (int j = 0; j < _adj[i].Count; j++) { edges.Add(_adj[i][j]); } } return edges.ToArray(); } } //边的类 public class Edge : IComparable { private float _weight; private int v; //一个结点 private int w; //另一个结点 public Edge(int v, int w, float weight) { this.v = v; this.w = w; this._weight = weight; } public float Weight => _weight; public int Either => v; /// <summary> /// 取得不同于参数的另一个结点 /// </summary> public int Other(int vertex) { if (vertex == v) return w; else if (vertex == w) return v; else throw new Exception("不包含该顶点"); } public int CompareTo(object? obj) { Edge other = (Edge)obj; if (this._weight > other.Weight) return 1; else if (this._weight < other.Weight) return -1; else return 0; } } }
实例的加权图数据
8 4 5 .35 4 7 .37 5 7 .28 0 7 .16 1 5 .32 0 4 .38 2 3 .17 1 7 .19 0 2 .26 1 2 .36 1 3 .29 2 7 .34 6 2 .40 3 6 .52 6 0 .58 6 4 .93
切分定理
将加权图的结点分为两个部分:已经确定为最小生成树的结点和尚未加入最小生成树的结点,这连接这两部分的边中最小的一条边可以加入最小生成树。获得最小生成树的算法大都基于切分定理
常用的算法:
Prim算法
延时Prim算法
描述:从一个结点开始将与之关联的边加入一个最小堆,然后从最小堆中得到权重最小的边,将边加入用于表示最小生成树的队列,同时遍历与该边连接的另一个结点的邻接表,将边都加入最小堆,重复这个过程直到最小堆为空。
所需空间: 与E成正比,
所需时间: 与ElogE成正比(最坏情况)
API:
public LazyPrimMST(EdgeWeightedGraph g) //构造函数 private void Visit(EdgeWeightedGraph g, int v) //添加v的未访问的邻接结点 public Edge[] Edges() //返回最小树边的数组
详细代码:
using System; using System.Collections.Generic; using System.Text; using SortFunction; namespace 图 { public class LazyPrimMST { private bool[] marked; //是否检查过该节点 private Queue<Edge> mst; private MinPQ<Edge> pq; public LazyPrimMST(EdgeWeightedGraph g) { marked = new bool[g.V]; mst = new Queue<Edge>(); pq = new MinPQ<Edge>(); Visit(g, 0); while (!pq.IsEmpty()) { Edge min = pq.DeleteMin(); int v = min.Either, w = min.Other(v); if (marked[v] && marked[w]) continue; //跳过失效的边 mst.Enqueue(min); if (!marked[v]) Visit(g, v); if (!marked[w]) Visit(g, w); } } /// <summary> /// 添加v的未访问的邻接结点 /// </summary> private void Visit(EdgeWeightedGraph g, int v) { marked[v] = true; foreach (Edge i in g.Adj(v)) { if (!marked[i.Other(v)]) pq.Insert(i); } } public Edge[] Edges() { return mst.ToArray(); } } }
即时Prim算法
描述:从延迟的prim算法中我们可以看到无效的边(边的两端都已经加入了最小生成树)也被加入到了最小堆里,但每次寻找边时我们需要的只是树外的点到树结点的权值最小的边,因此可以只将树外点w连接到树的边中权值最小的那个加入到最小堆(实际上是使用的索引最小堆)当中,这样可以避免在最小堆出堆时再检查边的有效性。
API: 同延时实现
所需空间: 与V成正比,
所需时间: 与ElogV成正比(最坏情况)
详细代码:
using SortFunction; using System; namespace 图 { public class Prim { private Edge[] edgeTo; private bool[] marked; private double[] distTo; private IndexMinPQ<double> pq; //索引最小堆,方便在找到更小的边时进行替换 public Prim(EdgeWeightedGraph g) { edgeTo = new Edge[g.V]; //记录点连接到生成树的边 marked = new bool[g.V]; distTo = new double[g.V]; //记录连接边的权值 pq = new IndexMinPQ<double>(); //未赋值时设置为最大 for (int i = 0; i < g.V; i++) { distTo[i] = Double.MaxValue; } distTo[0] = 0.0f; pq.Insert(0, 0.0f); while (!pq.IsEmpty()) { Visit(g, pq.DeleteMin()); } } private void Visit(EdgeWeightedGraph g, int v) { marked[v] = true; foreach (Edge e in g.Adj(v)) { int w = e.Other(v); if (marked[w]) continue; if (e.Weight < distTo[w]) { edgeTo[w] = e; distTo[w] = e.Weight; if (pq.Contains(w)) pq.Change(w, e.Weight); else pq.Insert(w, e.Weight); } } } } }
Kruskal算法
描述: 每次将权值最小的边加入最小堆中,然后依次出堆,直到树中有V-1条边为止(完成最小生成树),每次出堆要对结点的连通性进行检查,防止形成环
所需空间: 与E成正比
所需时间: 与ElogE成正比(最坏情况)
using SortFunction; using System.Collections.Generic; namespace 图 { public class KruskalMST { private Queue<Edge> mst; public KruskalMST(EdgeWeightedGraph g) { mst = new Queue<Edge>(); MinPQ<Edge> pq = new MinPQ<Edge>(); foreach (Edge e in g.Edges()) pq.Insert(e); UF uf = new UF(g.V); //uf可以进行连通性检查,本体是union-find算法 while (!pq.IsEmpty() && mst.Count < g.V - 1) { Edge e = pq.DeleteMin(); int v = e.Either, w = e.Other(v); if (uf.Connected(v, w)) continue; //如果这两个结点进通过边相连,则跳过 uf.Union(v, w); mst.Enqueue(e); } } public Edge[] Edges => mst.ToArray(); } }
相关文章推荐
- 基于c#的两种最小凸包的生成(三硬币法与串行算法)
- 数据结构(C#)--图结构的实现输出以及图结构的深度和广度优先搜索和Dijkstra 算法的最小路径以及最小生成树的实现
- 最小生成树 Building a Space Station 三维 作为范二青年我用了克鲁斯算法,而且很悲剧的是我很无奈的用g++交了一中午,然后WA= =。。。
- 图解最小生成树 - 普里姆(Prim)算法
- Kruskal最小生成树算法
- MST最小生成树及克鲁斯卡尔(Kruskal)算法
- 朱刘算法---有向图的最小生成树
- 一个简单的QQ隐藏图生成算法 通过jQuery和C#分别实现对.NET Core Web Api的访问以及文件上传
- 算法——最小生成树:Kruskal算法、Prim算法
- 最短路径和最小生成树算法
- 最小生成树的两种算法:Prim和Kruskal算法
- 【算法】图论_最小生成树(MST)_Kruskal
- 最小生成树算法
- C#生成安装文件后自动附加数据库的思路跟算法
- 算法分析与设计实践 - 作业1 - 分别采用Prim算法和Kruskal算法构建最小生成树
- 数据结构_图_最小生成树算法之prime算法
- 算法分析与设计作业1:Prim算法与Kruskal算法构造最小生成树
- 浅谈最小生成树的两种算法
- 最小生成树算法之Prim算法
- 【算法——04】最小生成树——Prim和Kruskal算法