【数据结构】有向图、无向图以及最短路(Dijkstra)算法的C#实现(Template模式)

 为了写个“运筹学”的小工具,发现必须用到数据结构中的图。找了一圈没有找到自己满意的,只能自己写一个。

所有代码基于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

 

posted @ 2009-07-12 17:51  会拍照能修图的码农  阅读(496)  评论(0)    收藏  举报