为了写个“运筹学”的小工具,发现必须用到数据结构中的图。找了一圈没有找到自己满意的,只能自己写一个。
所有代码基于C#,完全模板实现。
首先是顶点的定义,顶点可以是任意类型,但其ID为Int32,该ID只是Graph内部使用。
public sealed class GraphVertex<M> { #region Constructor public GraphVertex() { } #endregion #region Fields private Int32 nID = default(Int32); private M objData = default(M); #endregion #region Properties public Int32 ID { get { return nID; } set { nID = value; } } public M Data { get { return objData; } set { objData = value; } } #endregion }
然后是Edge的定义,这里有一个辅助的Struct:GraphEdgeCore,其负责真正的边的识别:
public struct GraphEdgeCore : IEquatable<GraphEdgeCore> { public GraphEdgeCore(Int32 i1, Int32 i2) { ID1 = i1; ID2 = i2; } public Int32 ID1; public Int32 ID2; public Boolean IsValidated() { if (ID1 == -1 || ID2 == -1) return false; if (ID1 == ID2) return false; return true; } #region IEquatable<GraphEdgeCore> Members public bool Equals(GraphEdgeCore other) { if (ID1 != other.ID1) return false; if (ID2 != other.ID2) return false; return true; } #endregion #region Operators public static Boolean operator == (GraphEdgeCore ec1, GraphEdgeCore ec2) { return ec1.Equals(ec2); } public static Boolean operator != (GraphEdgeCore ec1, GraphEdgeCore ec2) { return !(ec1 == ec2); } #endregion public override int GetHashCode() { return base.GetHashCode(); } public override bool Equals(object obj) { return (this == (GraphEdgeCore)obj); } } public sealed class GraphEdge<N> { #region Constructor public GraphEdge(Int32 n1, Int32 n2, N obj) { edge.ID1 = n1; edge.ID2 = n2; objData = obj; } #endregion #region Fields private GraphEdgeCore edge; private N objData = default(N); #endregion #region Properties public GraphEdgeCore EdgeCore { get { return edge; } } public N Data { get { return objData; } set { objData = value; } } #endregion }
最大的难点来了:既然全部是模板,就不可避免会遇到权重的比较,因为是模板,所以必须定义一个额外的辅助类来实现:
public abstract class ACGraphEdgeWeightAssistant<N> //: IComparable, IComparable<ACGraphEdgeWeightAssistant<N>> { #region Properties public abstract N MinimumValue { get; } public abstract N MaximumValue { get; } #endregion #region Abstract methods public abstract N Add(N n1, N n2); public abstract N Subtract(N n1, N n2); public abstract Boolean Equals(N n1, N n2); public abstract Int32 Compare(N n1, N n2); public abstract Boolean IsMaximumValue(N nVal); #endregion //#region IComparable<ACGraphEdgeWeightAssistant<N>> Members //public int CompareTo(ACGraphEdgeWeightAssistant<N> other) //{ // throw new NotImplementedException(); //} //#endregion //#region IComparable Members //public int CompareTo(object obj) //{ // return CompareTo(obj as ACGraphEdgeWeightAssistant<N>); //} //#endregion }
最后是图的定义:
public sealed class Graph<M, N> // where N : ValueType { #region Constructor public Graph(ACGraphEdgeWeightAssistant<N> objAss) { System.Diagnostics.Debug.Assert(objAss != null); pAssist = objAss; } #endregion #region Fields private Dictionary<Int32, GraphVertex<M>> dictNodes = new Dictionary<int, GraphVertex<M>>(); private List<GraphEdge<N>> listEdges = new List<GraphEdge<N>>(); private Int32 nVertexID = 0; // Assistant for working for N private ACGraphEdgeWeightAssistant<N> pAssist = null; #endregion #region Properties public Int32 NodesAmount { get { return dictNodes.Count; } } public Int32 EdgesAmount { get { return listEdges.Count; } } #endregion #region Public methods public void InsertVertex(GraphVertex<M> objVertex) { System.Diagnostics.Debug.Assert(objVertex != null); objVertex.ID = nVertexID++; dictNodes.Add(objVertex.ID, objVertex); } public void InsertEdge(GraphEdge<N> objEdge) { System.Diagnostics.Debug.Assert(objEdge != null); if (!objEdge.EdgeCore.IsValidated()) return; if (!dictNodes.ContainsKey(objEdge.EdgeCore.ID1)) return; if (!dictNodes.ContainsKey(objEdge.EdgeCore.ID2)) return; listEdges.Add(objEdge); } public GraphEdge<N>[] GetEdges(GraphEdgeCore objEdgeCore) { List<GraphEdge<N>> lists = new List<GraphEdge<N>>(); foreach (GraphEdge<N> ge in listEdges) { if (ge.EdgeCore == objEdgeCore) lists.Add(ge); } return lists.ToArray(); } public GraphEdge<N>[] GetEdges(Int32 nID, Boolean bSource) { List<GraphEdge<N>> lists = new List<GraphEdge<N>>(); foreach (GraphEdge<N> ge in listEdges) { if (bSource) { if (ge.EdgeCore.ID1 == nID) lists.Add(ge); } else { if (ge.EdgeCore.ID2 == nID) lists.Add(ge); } } return lists.ToArray(); } public GraphEdge<N>[] GetAllEdges() { return listEdges.ToArray(); } public GraphVertex<M>[] GetAllVerteics() { List<GraphVertex<M>> listVer = new List<GraphVertex<M>>(); foreach (GraphVertex<M> gv in dictNodes.Values) listVer.Add(gv); return listVer.ToArray(); } // Get shortest path // Note: call this method before you make sure that the weight should not negative. public void DijkstraShortestPath(Int32 nSource, Int32 nTarget, List<GraphPath<N>> listPaths) { System.Diagnostics.Debug.Assert(listPaths != null); listPaths.Clear(); GraphPath<N> gp = null; if (!dictNodes.ContainsKey(nSource)) return; if (!dictNodes.ContainsKey(nTarget)) return; if (nSource == nTarget) { gp = new GraphPath<N>(); gp.CurrentCost = pAssist.MinimumValue; gp.PathNodes.Add(nSource); listPaths.Add(gp); return; } Dictionary<Int32, N> dictPSet = new Dictionary<Int32, N>(); Dictionary<Int32, N> dictTSet = new Dictionary<Int32, N>(); // Initialize dictPSet.Add(nSource, pAssist.MinimumValue); foreach (Int32 nVer in dictNodes.Keys) { if (nVer != nSource) dictTSet.Add(nVer, pAssist.MaximumValue); } while (!dictPSet.ContainsKey(nTarget)) { // Go through each item in P Set foreach (Int32 nID in dictPSet.Keys) { foreach (GraphEdge<N> ge in GetEdges(nID, true)) { if (dictTSet.ContainsKey(ge.EdgeCore.ID2)) { // Update the T value: min { P(X) + W, T(X) } N nTValue = dictTSet[ge.EdgeCore.ID2]; N nPValue = dictPSet[nID]; N nVal = pAssist.Add(nPValue, ge.Data); if (pAssist.IsMaximumValue(nTValue)) { dictTSet[ge.EdgeCore.ID2] = nVal; } else { if (pAssist.Compare(nTValue, nVal) > 0) { dictTSet[ge.EdgeCore.ID2] = nVal; } } } } } // Get the minimum of TSet Int32 nTID = default(Int32); N ntmp = pAssist.MaximumValue; foreach (KeyValuePair<Int32, N> kvp in dictTSet) { if (pAssist.Compare(kvp.Value, ntmp) < 0) { ntmp = kvp.Value; nTID = kvp.Key; } } if (nTID == default(Int32)) break; dictTSet.Remove(nTID); dictPSet.Add(nTID, ntmp); } // OKay, we get the final result, parse the result out gp = new GraphPath<N>(); gp.CurrentID = nTarget; gp.CurrentCost = dictPSet[nTarget]; gp.PathNodes.Add(nTarget); listPaths.Add(gp); Boolean bEnd = false; Boolean bExisted = false; List<GraphPath<N>> listTempPaths = new List<GraphPath<N>>(); while (!bEnd) { bExisted = false; listTempPaths.Clear(); foreach (GraphPath<N> gpath in listPaths) { if (gpath.DeadPath) continue; foreach (GraphEdge<N> ge in GetEdges(gpath.CurrentID, false)) { //dictPSet[ge.Data] if (dictPSet.ContainsKey(ge.EdgeCore.ID1) && pAssist.Compare(gpath.CurrentCost, pAssist.Add(ge.Data, dictPSet[ge.EdgeCore.ID1])) == 0) { if (!bExisted) { gpath.CurrentID = ge.EdgeCore.ID1; gpath.PathNodes.Add(ge.EdgeCore.ID1); gpath.CurrentCost = dictPSet[ge.EdgeCore.ID1]; bExisted = true; } else { // Existed already in current GraphPath<N> gp2 = new GraphPath<N>(gpath); gp2.CurrentID = ge.EdgeCore.ID1; gp2.PathNodes.Add(ge.EdgeCore.ID1); gp2.CurrentCost = dictPSet[ge.EdgeCore.ID1]; listTempPaths.Add(gp2); } } } if (!bExisted) { gpath.DeadPath = true; } } foreach (GraphPath<N> gpath in listTempPaths) listPaths.Add(gpath); foreach (GraphPath<N> gpath in listPaths) { if ((!gpath.DeadPath) && gpath.PathNodes.Contains(nSource)) { bEnd = true; break; } } } // Finalize the result output foreach (GraphPath<N> gpth in listPaths) { if (gpth.DeadPath) listPaths.Remove(gpth); else gpth.CurrentCost = dictPSet[nTarget]; } } #endregion }
其中,代表路径使用了另外一个类:
public sealed class GraphPath<N> : ICloneable { #region Constructor public GraphPath() { } public GraphPath(GraphPath<N> other) : this() { if (other != null) { nCurID = other.nCurID; curCost = other.curCost; foreach (Int32 n in other.listPath) listPath.Add(n); } } #endregion #region Fields private List<Int32> listPath = new List<Int32>(); private Int32 nCurID = default(Int32); private N curCost = default(N); private Boolean bDeadPath = false; #endregion #region Properties public List<Int32> PathNodes { get { return listPath; } } public Int32 CurrentID { get { return nCurID; } set { nCurID = value; } } public N CurrentCost { get { return curCost; } set { curCost = value; } } public Boolean DeadPath { get { return bDeadPath; } set { bDeadPath = value; } } #endregion #region ICloneable Members public object Clone() { return new GraphPath<N>(this); } #endregion }
使用实例:
public sealed class ACGraphEdgeWeightSingleAssistant : ACGraphEdgeWeightAssistant<Single> { public override float MinimumValue { get { return 0; } } public override float MaximumValue { get { return Single.MaxValue; } } public override float Add(float n1, float n2) { return n1 + n2; } public override float Subtract(float n1, float n2) { return n1 – n2; } public override bool Equals(float n1, float n2) { return n1 == n2; } public override int Compare(float n1, float n2) { if (n1 > n2) return 1; if (n1 < n2) return -1; return 0; } public override bool IsMaximumValue(float nVal) { return nVal == Single.MaxValue; } } ACGraphEdgeWeightSingleAssistant objWSA = new ACGraphEdgeWeightSingleAssistant(); Dictionary<Char, Int32> dictVer = new Dictionary<Char, Int32>(); Graph<Char, Single> gph = new Graph<Char, Single>(objWSA);GraphVertex<Char> gv = new GraphVertex<char>(); gv.Data = ‘1’; gph.InsertVertex(gv); dictVer.Add(‘1’, gv.ID); gv = new GraphVertex<char>(); gv.Data = ‘2’; gph.InsertVertex(gv); dictVer.Add(‘2’, gv.ID); gv = new GraphVertex<char>(); gv.Data = ‘3’; gph.InsertVertex(gv); dictVer.Add(‘3’, gv.ID); gv = new GraphVertex<char>(); gv.Data = ‘4’; gph.InsertVertex(gv); dictVer.Add(‘4’, gv.ID); gv = new GraphVertex<char>(); gv.Data = ‘5’; gph.InsertVertex(gv); dictVer.Add(‘5’, gv.ID); gv = new GraphVertex<char>(); gv.Data = ‘6’; gph.InsertVertex(gv); dictVer.Add(‘6’, gv.ID); GraphEdge<Single> ge = new GraphEdge<float>(dictVer[‘1’], dictVer[‘2’], 4); gph.InsertEdge(ge); ge = new GraphEdge<float>(dictVer[‘1’], dictVer[‘3’], 3); gph.InsertEdge(ge); ge = new GraphEdge<float>(dictVer[‘2’], dictVer[‘4’], 3); gph.InsertEdge(ge); ge = new GraphEdge<float>(dictVer[‘2’], dictVer[‘5’], 2); gph.InsertEdge(ge); ge = new GraphEdge<float>(dictVer[‘3’], dictVer[‘5’], 3); gph.InsertEdge(ge); ge = new GraphEdge<float>(dictVer[‘4’], dictVer[‘6’], 2); gph.InsertEdge(ge); ge = new GraphEdge<float>(dictVer[‘5’], dictVer[‘6’], 2); gph.InsertEdge(ge); List<GraphPath<Single>> listPaths = new List<GraphPath<Single>>(); gph.DijkstraShortestPath(dictVer[‘1’], dictVer[‘6’], listPaths);
注意:对Dijkstra算法来说,其基本要求权重必须非负!根据上述代码可以轻易的实现对权重无要求的Floyd算法。
By Alva Chien.
2009.07.12