您的位置:首页 > 编程语言 > C#

最小生成树(C#)(算法第四版)

2020-04-08 21:58 19 查看

最小生成树 - 加权无向图(算法第四版)

约定

这里只简单的介绍一般情况下的最小生成树问题,因此最加权图数据做了一些规定:

  1. 只考虑连通图(如果不满足会产生最小森林)
  2. 所有边的权重不同(不满足会出现多个最小生成树)

加权图数据结构

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();
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: