using System; using System.Collections; using System.Collections.Generic; using System.Text; namespace Eppstein { #region HelperClasses /// /// Class for nodes in shortest path tree, with comparing capability /// public class SP_Node : IComparable { /// /// Contained edge /// public Edge Edge; /// /// Weight of node (edge) /// public int Weight; /// /// Public constructor /// /// Edge object /// Weight of node public SP_Node(Edge _edge, int _weight) { this.Edge = _edge; this.Weight = _weight; } /// /// Implements IComparable's CompareTo method /// Compare two SP_Node objects by it weight values /// /// Object to compare with /// -1 if this is shorter than object, 1 if countersense, 0 if both are equal /// Thrown when obj is not SP_Node type int IComparable.CompareTo(object _obj) { return Math.Sign(this.Weight - ((SP_Node)_obj).Weight); } /// /// Returns node's descriptive string /// /// Edge and weight string public override string ToString() { return /*this.Edge+*/" ["+Weight+"]"; } } /// /// Sidetracks collection, with comparing capability /// public class ST_Node : IComparable { /// /// Collection of edges (sidetracks), not exactly a full path /// public Path Sidetracks; /// /// Weight of node (sum of sidetracks) /// public int Weight; /// /// Public constructor /// /// Sidetrack collection /// Weight is calculated using path's weight public ST_Node(Path _sidetracks) { this.Sidetracks = _sidetracks; this.Weight = _sidetracks.DeltaWeight; } /// /// Implements IComparable's CompareTo method /// Compare two ST_Node objects by it weight values /// /// Object to compare with /// -1 if this is shorter than object, 1 if countersense, 0 if both are equal /// Thrown when obj is not ST_Node type int IComparable.CompareTo(object _obj) { return Math.Sign(this.Weight - ((ST_Node)_obj).Weight); } /// /// Returns node's descriptive string /// /// Weight as string public override string ToString() { return /*this.Sidetracks+*/" ["+Weight+"]"; } } #endregion /// /// Contains a directed graph, composed by vertices and directed edges collections /// public class Graph { #region Private fields /// /// Linear array of vertices /// private List Vertices; /// /// Collections of Sidetracks in graph, ordered by total delta /// private PriorityQueue PathsHeap; /// /// Flag to indicate if shortest paths have been recalculated /// private bool Ready; /// /// Source vertex, is valid after main shortest path calculation /// private Vertex SourceVertex; /// /// Endpoint vertex, is valid after main shortest path calculation /// private Vertex EndVertex; #endregion #region Public methods /// /// Default constructor /// public Graph() { Vertices = new List(); Ready = false; } /// /// Creates all vertices in graph by parsing a string, remove all previous vertices and edges on lists /// /// String containing a comma-separated list with all vertex labels /// True if all labels well parsed, false if not /// A label must start with a letter and have all remain characters letters or digits /// Repeated and empty labels with be ignored public bool CreateVertices(string _vertices) { // Scans vertices' string, creates objects, and add to vertices' list Ready = false; // must rebuild trees Vertices.Clear(); // Remove all previous vertices string[] vertexList = _vertices.ToUpper().Split(new char[] { ',' }); foreach (string _label in vertexList) { if (_label.Length == 0) // ignore empty labels continue; if (!char.IsLetter(_label, 0)) // label must start with a letter return false; foreach (char c in _label) { if (!char.IsLetterOrDigit(c)) // label just can contain letters and digits return false; } if (GetVertex(_label)==null) Vertices.Add(new Vertex(_label)); } return true; } /// /// Creates directional edges /// /// Comma-separated list of vertices for tails /// Comma-separated list of vertices for heads /// Weight for all edges /// Group name for all edges /// True if vertices are valid /// Count of tails must match count of heads, all vertices must exist public bool CreateEdges(string _tails, string _heads, int _weight, string _group) { List tails = this.ParseVertices(_tails.ToUpper()); List heads = this.ParseVertices(_heads.ToUpper()); string group = _group.ToUpper(); if (tails==null || heads==null) return false; if (tails.Count != heads.Count) return false; for (int i = 0; i < tails.Count; i++) { Edge e = new Edge(tails[i], heads[i], _weight, group); tails[i].RelatedEdges.Add(e); if (tails[i] != heads[i]) // Avoids duplicated reference for self-pointing edges heads[i].RelatedEdges.Add(e); } Ready = false; return true; } /// /// Change group weights /// /// Group name of edges to be affected /// New weights public void EdgeGroupWeights(string _group, int _weight) { string group = _group.ToUpper(); foreach (Vertex _v in Vertices) foreach (Edge _e in _v.RelatedEdges) if (_e.Group == group) _e.Weight = _weight; this.Ready = false; } /// /// Calculates all shortest paths between s and t and returns the first one /// /// Label of initial vertex /// Label of ending vertex /// Directional path if found, empty path if not or labels not valid public Path FindShortestPath(string _s, string _t) { Ready = false; // Parse start and end vertex strings this.SourceVertex = this.GetVertex(_s.ToUpper()); this.EndVertex = this.GetVertex(_t.ToUpper()); if (SourceVertex == null || EndVertex == null) return new Path(); // Invalid path // Builds shortest path tree for all vertices to t, according to Dijkstra, // storing distance to endpoint information on vertices, as described in Eppstein BuildShortestPathTree(); // Fills a heap with all possible tracks from s to t, as described in Eppstein // Paths are defined uniquely by sidetrack collections (edges not in shortest paths) BuildSidetracksHeap(); // Flag to indicate that shortest paths have been calculated Ready = true; return FindNextShortestPath(); } /// /// Recover next pre-calculated shortest path /// /// Directional path if available, empty path if not remaining paths public Path FindNextShortestPath() { if (!Ready) return new Path(); // Invalid path // Pick next track from heap, it is ordered from shortest path to longest ST_Node node = this.PathsHeap.Dequeue(); if (node == null) return new Path(); // Invalid path // Returns path reconstructed from sidetracks return RebuildPath(node.Sidetracks); } #endregion #region Private methods /// /// Parses a comma-separated list of vertices /// /// Comma-separated list of vertex labels /// List of vertices, null if some vertex does not exist private List ParseVertices(string _labels) { string[] vertexList = _labels.ToUpper().Split(new char[] { ',' }); if (vertexList.Length == 0) return null; List result = new List(); foreach (string _label in vertexList) { Vertex v = this.GetVertex(_label); // Check if label exists if (v==null) return null; result.Add(v); } return result; } /// /// Returns a reference for existing vertex /// /// Label of vertex to search for /// Vertex reference, null if not fount or empty label private Vertex GetVertex(string _label) { if (string.IsNullOrEmpty(_label)) return null; foreach (Vertex _v in Vertices) { if (_v.Equals(_label)) return _v; } return null; } /// /// Clears pointers to next edge in shortest path for all vertices /// Clears distances to endpoint in shortet path vor all vertices /// private void ResetGraphState() { foreach (Vertex _v in this.Vertices) { _v.EdgeToPath = null; _v.Distance = int.MinValue; } } /// /// Builds the shortest path tree using a priority queue for given vertex /// /// Negative edges are ignored private void BuildShortestPathTree() { ResetGraphState(); // Reset all distances to endpoint and previous shortest path Vertex v = this.EndVertex; v.Distance = 0; // Set distance to 0 for endpoint vertex // Creates a fringe (queue) for storing edge pending to be processed PriorityQueue fringe = new PriorityQueue(this.Vertices.Count); // Main loop do { if (v!=null) foreach (Edge _e in v.RelatedEdges) // Evaluate all incoming edges if (_e.Head==v && _e.Weight>=0) // Ignore negative edges fringe.Enqueue(new SP_Node(_e, _e.Weight + _e.Head.Distance)); SP_Node node = fringe.Dequeue(); // Extracts next element in queue if (node == null) // No pending edges to evaluate, finished break; Edge e = node.Edge; v = e.Tail; if (v.Distance == int.MinValue) // Vertex distance to endpoint not calculated yet { v.Distance = e.Weight + e.Head.Distance; v.EdgeToPath = e; } else v = null; } while (true); } /// /// Creates all posible paths by describing only the sidetracks for each path /// /// private void BuildSidetracksHeap() { this.PathsHeap = new PriorityQueue(Vertices.Count); Path empty = new Path(); this.PathsHeap.Enqueue(new ST_Node(empty)); AddSidetracks(empty, this.SourceVertex); } /// /// Adds sidetracks recursively for specified vertex and new vertices in shortest path /// /// Previous sidetrack collection /// Vertex to evalueate private void AddSidetracks(Path _p, Vertex _v) { foreach (Edge _e in _v.RelatedEdges) { if (_e.IsSidetrackOf(_v) && (_e.Head.EdgeToPath != null || _e.Head == this.EndVertex)) { Path p = new Path(); p.AddRange(_p); p.Add(_e); this.PathsHeap.Enqueue(new ST_Node(p)); if (_e.Head != _v) // This avoids infinite cycling AddSidetracks(p, _e.Head); } } if (_v.Next != null) AddSidetracks(_p, _v.Next); } /// /// Reconstructs path from sidetracks /// /// Sidetracks collections for this path, could be empty for shortest /// Full path reconstructed from s to t, crossing sidetracks private Path RebuildPath(Path _sidetracks) { Path path = new Path(); Vertex v = this.SourceVertex; int i = 0; // Start from s, following shortest path or sidetracks while (v != null) { // if current vertex is conected to next sidetrack, cross it if (i < _sidetracks.Count && _sidetracks[i].Tail == v) { path.Add(_sidetracks[i]); v = _sidetracks[i++].Head; } else // else continue walking on shortest path { if (v.EdgeToPath == null) break; path.Add(v.EdgeToPath); v = v.Next; } } return path; } #endregion } }