/* Copyright (c) 2003-2006 Niels Kokholm and Peter Sestoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // C5 example: Graph representation with basic algorithms using C5 // Compile with // csc /r:C5.dll Graph.cs // The code is structured as a rudimentary Graph library, with an interface // for (edge)weighted graphs and a single implementation based on an // adjacency list representation using hash dictionaries. // The algorithms implemented include: // // Breadth-First-Search and Depth-First-Search, with an interface based on actions // to be taken as edges are traversed. Applications are checking for connectedness, // counting components // // Priority-First-Search, where edges are traversed according to either weight or // accumulated weight from the start of the search. // An application of the non-accumulating version is the construction of a minimal // spanning tree and therefore the following approximation algorithm. // Applications of the accumulating version are the construction of a shortest path // and the computation of the distance from one vertex to another one. // // An approximation algorithm for Travelling Salesman Problems, // where the weights satisfies the triangle inequality. // Pervasive generic parameters: // V: The type of a vertex in a graph. Vertices are identified // by the Equals method inherited from object (or overridden). // E: The type of additional data associated with edges in a graph. // W: The type of values of weights on edges in a weighted graph, // in practise usually int or double. Must be comparable and // there must be given a compatible way to add values. // Interfaces: // IGraph: The interface for a graph implementation with // vertex type V, edge dat type E and weight values of type W. // IWeight: The interface of a weight function // Classes: // HashGraph: An implementation of IWeightedGraph based on // adjacency lists represented as hash dictionaries. // HashGraph.EdgesValue: A helper class for the Edges() method // CountWeight: A // IntWeight: // DoubleWeight: // Value Types: // Edge: // EdgeAction: // Some notes: // The code only supports "natural" equality of vertices. using C5; using System; using SCG = System.Collections.Generic; namespace Graph { /// /// (Duer ikke) /// /// /// interface IGraphVertex where W : IComparable { V Value { get;} IGraph Graph { get;} ICollectionValue> Adjacent { get;} } class Vertex { //V v; } /// /// /// /// /// /// interface IGraph where W : IComparable { /// /// /// /// The weight object for this graph IWeight Weight { get;} /// /// /// /// The number of vertices in this graph int VertexCount { get;} /// /// /// /// The number of edges in this graph int EdgeCount { get;} /// /// The collection of vertices of this graph. /// The return value is a snapshot, not a live view onto the graph. /// /// ICollectionValue Vertices(); /// /// The collection of edges incident to a particular vertex. /// The return value is a snapshot og (endvertex, edgedata) pairs. /// /// /// ICollectionValue> Adjacent(V vertex); /// /// The collection of all edges in the graph. The return value is a snapshot /// of Edge values. Each edge is present once for an undefined direction. /// /// ICollectionValue> Edges(); /// /// Add a(n isolated) vertex to the graph Ignore if vertex is already in the graph. /// /// /// True if the vertex was added. bool AddVertex(V vertex); /// /// Add an edge to the graph. If the edge is already in the graph, update the /// edge data. Add vertices as needed. /// /// /// /// /// True if the edge was added bool AddEdge(V start, V end, E edgedata); /// /// Remove a vertex and all its incident edges from the graph. /// /// True if the vertex was already in the graph and hence was removed bool RemoveVertex(V vertex); /// /// Remove an edge from the graph. Do not remove the vertices if they become isolated. /// /// /// /// On output, the edge data associated with the removed edge. /// True if bool RemoveEdge(V start, V end, out E edgedata); /// /// Is there an edge from start to end in this graph, and if so, what is /// the data on that edge. /// /// /// /// /// bool FindEdge(V start, V end, out E edge); /// /// Construct the subgraph corresponding to a set of vertices. /// /// /// IGraph SubGraph(ICollectionValue vs); /// /// /// /// True if graph is connected bool IsConnected { get; } /// /// /// /// The number of connected components of this graph int ComponentCount { get;} /// /// Compute the connnected components of this graph. /// /// A collection of (vertex,component) pairs, where the first part of the /// pair is some vertex in the component. ICollectionValue>> Components(); /// /// Traverse the connected component containing the start vertex, /// in either BFS or DFS order, beginning at start and performing the action /// act on each edge part of the constructed tree. /// /// True if BFS, false if DFS /// The vertex to start at /// The action to perform at each node void TraverseVertices(bool bfs, V start, Action> act); /// /// Traverse an undirected graph in either BFS or DFS order, performing the action /// act on each vertex. /// The start vertex of each component of the graph is undefinded. /// /// True if BFS, false if DFS /// void TraverseVertices(bool bfs, Action act); /// /// Traverse an undirected graph in either BFS or DFS order, performing the action /// act on each edge in the traversal and beforecomponent/aftercomponent /// at the start and end of each component (with argument: the start vertex of the component). /// /// True if BFS, false if DFS /// /// /// void TraverseVertices(bool bfs, Action> act, Action beforecomponent, Action aftercomponent); /// /// A more advanced Depth First Search traversal. /// /// The vertex to start the search at /// Action to perform when a vertex is first encountered. /// Action to perform when all edges out of a vertex has been handles. /// Action to perform as an edge is traversed. /// Action to perform when an edge is travesed back. /// Action to perform when an edge (a backedge)is seen, but not followed. void DepthFirstSearch(V start, Action beforevertex, Action aftervertex, Action> onfollow, Action> onfollowed, Action> onnotfollowed); //TODO: perhaps we should avoid exporting this? /// /// Traverse the part of the graph reachable from start in order of least distance /// from start according to the weight function. Perform act on the edges of the /// traversal as they are recognised. /// /// /// /// /// void PriorityFirstTraverse(bool accumulating, V start, EdgeAction act); /// /// Compute the (a) shortest path from start to end. THrow an exception if end cannot be reached rom start. /// /// /// /// /// ICollectionValue> ShortestPath(V start, V end); /// /// Compute the Distance from start to end, i.e. the total weight of a shortest path from start to end. /// Throw an exception if end cannot be reached rom start. /// /// /// /// W Distance(V start, V end); /// /// Compute a minimum spanning tree for the graph. /// Throw an exception if this graph is not connected. /// /// (The starting point of the PFS, to be removed) /// IGraph MinimumSpanningTree(out V root); /// /// Compute a factor 2 approximation to a Minimum Weight /// Perfect Matching in a graph using NNs /// /// ICollectionValue> ApproximateMWPM(); /// /// Construct a closed Euler tour of this graph if one exists, i.e. if /// the graph is connected and all vertices have even degrees. Throw an /// ArgumentException if no closed Euler tour exists. /// /// A list of vertices in an Euler tour of this graph. IList EulerTour(); /// /// This is intended for implementations of the very simple factor 2 approximation /// algorithms for the travelling salesman problem for Euclidic weight/distance /// functions, i.e. distances that satisfy the triangle inequality. (We do not do 3/2) /// /// IDirectedCollectionValue ApproximateTSP(); /// /// Pretty-print the graph to the console (for debugging purposes). /// void Print(System.IO.TextWriter output); } /// /// The type of an edge in a graph. An edge is identified by its pair of /// vertices. The pair is considered ordered, and so an Edge really describes /// an edge of the graph in a particular traversal direction. /// /// The type of a vertex. /// The type of data asociated with edges. struct Edge { static SCG.IEqualityComparer vequalityComparer = EqualityComparer.Default; public readonly V start, end; public readonly E edgedata; public Edge(V start, V end, E edgedata) { if (vequalityComparer.Equals(start, end)) throw new ArgumentException("Illegal: start and end are equal"); this.start = start; this.end = end; this.edgedata = edgedata; } public Edge Reverse() { return new Edge(end, start, edgedata); } public override string ToString() { return String.Format("(start='{0}', end='{1}', edgedata='{2}')", start, end, edgedata); ; } public override bool Equals(object obj) { if (obj is Edge) { Edge other = (Edge)obj; return vequalityComparer.Equals(start, other.start) && vequalityComparer.Equals(end, other.end); } return false; } /// /// /// /// public override int GetHashCode() { //TODO: should we use xor? Or a random factor? return start.GetHashCode() + 148712671 * end.GetHashCode(); } /// /// The unordered equalityComparer compares edges independent of the order of the vertices. /// public class UnorderedEqualityComparer : SCG.IEqualityComparer> { /// /// Check if two edges have the same vertices irrespective of order. /// /// /// /// public bool Equals(Edge i1, Edge i2) { return (vequalityComparer.Equals(i1.start, i2.start) && vequalityComparer.Equals(i1.end, i2.end)) || (vequalityComparer.Equals(i1.end, i2.start) && vequalityComparer.Equals(i1.start, i2.end)); } /// /// Return a hash code compatible with the unordered equals. /// /// /// public int GetHashCode(Edge item) { return item.start.GetHashCode() ^ item.end.GetHashCode(); } } } /// /// The type of the weight object of a graph. This consists of a function mapping /// edge data values to weight values, and an operation to add two weight values. /// It is required that weight values are comparable. /// /// The user must assure that the add operation is commutative and fulfills /// Add(w1,w2) ≤ w1 for all w1 and w2 that can appear as weights or sums of /// weights. In practise, W will be int or double, all weight values will be /// non-negative and the addition will be the natural addition on W. /// /// /// interface IWeight where W : IComparable { /// /// Compute the weight value corresponding to specific edge data. /// /// /// W Weight(E edgedata); /// /// Add two weight values. /// /// /// /// W Add(W w1, W w2); } /// /// An action to perform when an edge is encountered during a traversal of the graph. /// The "extra" parameter is for additional information supplied by the traversal /// algorithm. /// The intention of the bool return value is that returning false is a signal to the /// traversal algorithm to abandon the traversl because the user has already found /// what he was looking for. /// /// /// /// /// /// /// delegate bool EdgeAction(Edge edge, U extra); /* For a dense graph, we would use data fields: E'[,] or E'[][] for the matrix. Possibly E'[][] for a triangular one! Here E' = struct{E edgedata, bool present} or class{E edgedata}, or if E is a class just E. Thus E' is E! for value types. Or we could have two matrices: E[][] and bool[][]. HashDictionary to map vertex ids to indices. ArrayList for the map the other way. Or simply a HashedArrayList to get both? PresentList, FreeList or similar, if we do not want to compact the indices in the matrix on each delete. If we compact, we always do a delete on the vertex<->index map by a replace and a removelast: vimap[ind]=vimap[vimap.Count]; vimap.RemoveLast(); //also reorder matrix! */ /// /// An implementation of IGraph≤V,E,W≥ based on an adjacency list representation using hash dictionaries. /// As a consequence, this will be most efficient for sparse graphs. /// /// /// /// class HashGraph : IGraph where W : IComparable { int edgecount; HashDictionary> graph; IWeight weight; public IWeight Weight { get { return weight; } } /// /// Create an initially empty graph. /// /// [UsedBy("testTSP")] public HashGraph(IWeight weight) { this.weight = weight; edgecount = 0; graph = new HashDictionary>(); } /// /// Constructing a graph with no isolated vertices given a collection of edges. /// /// [UsedBy()] public HashGraph(IWeight weight, SCG.IEnumerable> edges) : this(weight) { foreach (Edge edge in edges) { if (edge.start.Equals(edge.end)) throw new ApplicationException("Edge has equal start and end"); { HashDictionary edgeset; //TODO: utilize upcoming FindOrAddSome operation if (!graph.Find(edge.start, out edgeset)) graph.Add(edge.start, edgeset = new HashDictionary()); if (!edgeset.UpdateOrAdd(edge.end, edge.edgedata)) edgecount++; if (!graph.Find(edge.end, out edgeset)) graph.Add(edge.end, edgeset = new HashDictionary()); edgeset.UpdateOrAdd(edge.start, edge.edgedata); } } } /// /// This constructs a graph with a given set of vertices. /// Will only allow these vertices. /// Duplicate edges are allowed. /// /// /// public HashGraph(IWeight weight, SCG.IEnumerable vertices, SCG.IEnumerable> edges) : this(weight) { foreach (V v in vertices) graph.Add(v, new HashDictionary()); foreach (Edge edge in edges) { HashDictionary edgeset; if (edge.start.Equals(edge.end)) throw new ApplicationException("Edge has equal start and end"); if (!graph.Find(edge.start, out edgeset)) throw new ApplicationException("Edge has unknown start"); if (!edgeset.UpdateOrAdd(edge.end, edge.edgedata)) edgecount++; if (!graph.Find(edge.end, out edgeset)) throw new ApplicationException("Edge has unknown end"); edgeset.UpdateOrAdd(edge.start, edge.edgedata); } } [UsedBy("testCOMP")] public int VertexCount { get { return graph.Count; } } [UsedBy("testCOMP")] public int EdgeCount { get { return edgecount; } } public ICollectionValue Vertices() { return new GuardedCollectionValue(graph.Keys); } public ICollectionValue> Adjacent(V vertex) { return new GuardedCollectionValue>(graph[vertex]); } class EdgesValue : CollectionValueBase> { HashGraph graph; internal EdgesValue(HashGraph g) { graph = g; } public override bool IsEmpty { get { return graph.edgecount == 0; } } public override int Count { get { return graph.edgecount; } } public override Speed CountSpeed { get { return Speed.Constant; } } public override Edge Choose() { KeyValuePair> adjacent = graph.graph.Choose(); KeyValuePair otherend = graph.graph[adjacent.Key].Choose(); return new Edge(adjacent.Key, otherend.Key, otherend.Value); } public override SCG.IEnumerator> GetEnumerator() { HashSet> seen = new HashSet>(new Edge.UnorderedEqualityComparer()); foreach (V v in graph.graph.Keys) foreach (KeyValuePair p in graph.graph[v]) { Edge edge = new Edge(v, p.Key, p.Value); if (!seen.FindOrAdd(ref edge)) yield return edge; } } } public ICollectionValue> Edges() { return new EdgesValue(this); } public bool AddVertex(V v) { if (graph.Contains(v)) return false; graph.Add(v, new HashDictionary()); return true; } //Note: no warning on update of edgedata! //TODO: Shouldn´t Update or Add return the old value? //Then it would be easy to check for updates public bool AddEdge(V start, V end, E edgedata) { bool retval = false; HashDictionary edgeset; if (graph.Find(start, out edgeset)) retval = !edgeset.UpdateOrAdd(end, edgedata); else { graph[start] = edgeset = new HashDictionary(); edgeset[end] = edgedata; retval = true; } if (graph.Find(end, out edgeset)) edgeset.UpdateOrAdd(start, edgedata); else { graph[end] = edgeset = new HashDictionary(); edgeset[start] = edgedata; } if (retval) edgecount++; return retval; } public bool RemoveVertex(V vertex) { HashDictionary edgeset; if (!graph.Find(vertex, out edgeset)) return false; foreach (V othervertex in edgeset.Keys) graph[othervertex].Remove(vertex); //Assert retval==true edgecount -= edgeset.Count; graph.Remove(vertex); return true; } public bool RemoveEdge(V start, V end, out E edgedata) { HashDictionary edgeset; if (!graph.Find(start, out edgeset)) { edgedata = default(E); return false; } if (!edgeset.Remove(end, out edgedata)) return false; graph[end].Remove(start); edgecount--; return true; } public bool FindEdge(V start, V end, out E edgedata) { HashDictionary edges; if (!graph.Find(start, out edges)) { edgedata = default(E); return false; } return edges.Find(end, out edgedata); } public IGraph SubGraph(ICollectionValue vs) { HashSet vertexset = vs as HashSet; if (vertexset == null) { vertexset = new HashSet(); vertexset.AddAll(vs); } return new HashGraph(weight, vs, Edges().Filter(delegate(Edge e) { return vertexset.Contains(e.start) && vertexset.Contains(e.end); })); } public bool IsConnected { //TODO: optimize: needs to change Action> to EdgeAction to be able to break out get { return ComponentCount <= 1; } } public int ComponentCount { get { int components = 0; TraverseVertices(false, null, delegate(V v) { components++; }, null); return components; } } public ICollectionValue>> Components() { ArrayList>> retval = new ArrayList>>(); HashGraph component; ArrayList vertices = null; Action> edgeaction = delegate(Edge e) { vertices.Add(e.end); }; Action beforecomponent = delegate(V v) { vertices = new ArrayList(); vertices.Add(v); }; Action aftercomponent = delegate(V v) { //component = SubGraph(vertices); component = new HashGraph(weight); foreach (V start in vertices) { //component.graph[start] = graph[start].Clone(); HashDictionary edgeset = component.graph[start] = new HashDictionary(); foreach (KeyValuePair adjacent in graph[start]) edgeset[adjacent.Key] = adjacent.Value; } retval.Add(new KeyValuePair>(v, component)); }; TraverseVertices(false, edgeaction, beforecomponent, aftercomponent); return retval; } [UsedBy("test1")] public void TraverseVertices(bool bfs, V start, Action> act) { if (!graph.Contains(start)) throw new ArgumentException("start Vertex not in graph"); IList> todo = new LinkedList>(); todo.FIFO = bfs; HashSet seen = new HashSet(); V v; while (!todo.IsEmpty || seen.Count == 0) { if (seen.Count > 1) { Edge e = todo.Remove(); if (act != null) act(e); v = e.end; } else { seen.Add(start); v = start; } HashDictionary adjacent; if (graph.Find(v, out adjacent)) { foreach (KeyValuePair p in adjacent) { V end = p.Key; if (!seen.FindOrAdd(ref end)) todo.Add(new Edge(v, end, p.Value)); } } } } public void TraverseVertices(bool bfs, Action act) { TraverseVertices(bfs, delegate(Edge e) { act(e.end); }, act, null); } //TODO: merge the hash set here with the intra omponent one? public void TraverseVertices(bool bfs, Action> act, Action beforecomponent, Action aftercomponent) { HashSet missing = new HashSet(); missing.AddAll(Vertices()); Action> myact = act + delegate(Edge e) { missing.Remove(e.end); }; Action mybeforecomponent = beforecomponent + delegate(V v) { missing.Remove(v); }; while (!missing.IsEmpty) { V start = default(V); foreach (V v in missing) { start = v; break; } mybeforecomponent(start); TraverseVertices(bfs, start, myact); if (aftercomponent != null) aftercomponent(start); } } delegate void Visitor(V v, V parent, bool atRoot); //TODO: allow actions to be null [UsedBy("testDFS")] public void DepthFirstSearch(V start, Action before, Action after, Action> onfollow, Action> onfollowed, Action> onnotfollowed) { HashSet seen = new HashSet(); seen.Add(start); //If we do not first set visit = null, the compiler will complain at visit(end) //that visit is uninitialized Visitor visit = null; visit = delegate(V v, V parent, bool atRoot) { before(v); HashDictionary adjacent; if (graph.Find(v, out adjacent)) foreach (KeyValuePair p in adjacent) { V end = p.Key; Edge e = new Edge(v, end, p.Value); if (!seen.FindOrAdd(ref end)) { onfollow(e); visit(end, v, false); onfollowed(e); } else { if (!atRoot && !parent.Equals(end)) onnotfollowed(e); } } after(v); }; visit(start, default(V), true); } public void PriorityFirstTraverse(bool accumulated, V start, EdgeAction act) { if (!graph.Contains(start)) throw new ArgumentException("Graph does not contain start"); IPriorityQueue fringe = new IntervalHeap(); HashDictionary> seen = new HashDictionary>(); HashDictionary, Edge> bestedge = new HashDictionary, Edge>(); IPriorityQueueHandle h = null; V current; W currentdist; while (!fringe.IsEmpty || seen.Count == 0) { if (seen.Count == 0) { seen.Add(start, h); current = start; currentdist = default(W); } else { currentdist = fringe.DeleteMin(out h); Edge e = bestedge[h]; if (!act(e, currentdist)) break; bestedge.Remove(h); current = e.end; } HashDictionary adjacentnodes; if (graph.Find(current, out adjacentnodes)) foreach (KeyValuePair adjacent in adjacentnodes) { V end = adjacent.Key; E edgedata = adjacent.Value; W dist = weight.Weight(edgedata), olddist; if (accumulated && !current.Equals(start)) dist = weight.Add(currentdist, weight.Weight(edgedata)); if (!seen.Find(end, out h)) { h = null; fringe.Add(ref h, dist); seen[end] = h; bestedge[h] = new Edge(current, end, edgedata); } else if (fringe.Find(h, out olddist) && dist.CompareTo(olddist) < 0) { fringe[h] = dist; bestedge[h] = new Edge(current, end, edgedata); } } } } public W Distance(V start, V end) { W dist = default(W); bool found = false; PriorityFirstTraverse(true, start, delegate(Edge e, W w) { if (end.Equals(e.end)) { dist = w; found = true; return false; } else return true; }); if (found) return dist; throw new ArgumentException(String.Format("No path from {0} to {1}", start, end)); } public ICollectionValue> ShortestPath(V start, V end) { HashDictionary> backtrack = new HashDictionary>(); PriorityFirstTraverse(true, start, delegate(Edge e, W w) { backtrack[e.end] = e; return !end.Equals(e.end); }); ArrayList> path = new ArrayList>(); Edge edge; V v = end; while (backtrack.Find(v, out edge)) { path.Add(edge); v = edge.start; } if (path.IsEmpty) throw new ArgumentException(String.Format("No path from {0} to {1}", start, end)); path.Reverse(); return path; } /// /// NB: assume connected, throw exception if not /// /// /// /// public IGraph MinimumSpanningTree(out V start) { ArrayList> edges = new ArrayList>(); start = default(V); foreach (V v in graph.Keys) { start = v; break; } PriorityFirstTraverse(false, start, delegate(Edge e, W w) { edges.Add(e); return true; }); if (edges.Count != graph.Count - 1) throw new ArgumentException("Graph not connected"); return new HashGraph(weight, edges); } public ICollectionValue> ApproximateMWPM() { //Assume graph complete and even number of vertices HashGraph clone = new HashGraph(weight, Edges()); ArrayList> evenpath = new ArrayList>(); ArrayList> oddpath = new ArrayList>(); V start = default(V); foreach (V v in clone.Vertices()) { start = v; break; } V current = start; W evenweight, oddweight; evenweight = oddweight = default(W); bool even = true; while (clone.VertexCount > 0) { V bestvertex = default(V); E bestedge = default(E); W bestweight = default(W); if (clone.VertexCount == 1) { bestvertex = start; bestedge = graph[current][start]; bestweight = weight.Weight(bestedge); } else { bool first = true; foreach (KeyValuePair p in clone.graph[current]) { W thisweight = weight.Weight(p.Value); if (first || bestweight.CompareTo(thisweight) > 0) { bestvertex = p.Key; bestweight = thisweight; bestedge = p.Value; } first = false; } } clone.RemoveVertex(current); //Console.WriteLine("-* {0} / {1} / {2}", bestvertex, bestweight, tour.Count); if (even) { evenweight = evenpath.Count < 1 ? bestweight : weight.Add(evenweight, bestweight); evenpath.Add(new Edge(current, bestvertex, bestedge)); } else { oddweight = oddpath.Count < 1 ? bestweight : weight.Add(oddweight, bestweight); oddpath.Add(new Edge(current, bestvertex, bestedge)); } current = bestvertex; even = !even; } //Console.WriteLine("Totalweights: even: {0} and odd: {1}", evenweight, oddweight); return evenweight.CompareTo(oddweight) < 0 ? evenpath : oddpath; } /// /// The construction is performed as follows: /// Start at some vertex. Greedily construct a path starting there by /// following edges at random until no more unused edges are available /// from the current node, which must be the start node. Then follow /// the path constructed so far and whenever we meet a vertex with /// unused edges, construct a path from there greedily as above, /// inserting into the path in front of us. /// /// The algorithm will use constant time for each vertex added /// to the path and /// /// Illustrates use of views as a safe version of listnode pointers /// and hashed linked lists for choosing some item in constant time combined /// with (expected) constant time remove. /// /// public IList EulerTour() { bool debug = false; //Assert connected and all degrees even. (Connected is checked at the end) string fmt = "Graph does not have a closed Euler tour: vertex {0} has odd degree {1}"; foreach (KeyValuePair> adj in graph) if (adj.Value.Count % 2 != 0) throw new ArgumentException(String.Format(fmt, adj.Key, adj.Value.Count)); LinkedList path = new LinkedList(); //Clone the graph data to keep track of used edges. HashDictionary> edges = new HashDictionary>(); V start = default(V); HashedArrayList adjacent = null; foreach (KeyValuePair> p in graph) { adjacent = new HashedArrayList(); adjacent.AddAll(p.Value.Keys); start = p.Key; edges.Add(start, adjacent); } path.Add(start); IList view = path.View(0, 1); while (adjacent.Count > 0) { V current = view[0]; if (debug) Console.WriteLine("==> {0}", current); //Augment the path (randomly) until we return here and all edges while (adjacent.Count > 0) { if (debug) Console.WriteLine(" => {0}, {1}", current, path.Count); V next = adjacent.RemoveFirst(); view.Add(next); if (debug) Console.WriteLine("EDGE: " + current + "->" + next); if (!edges[next].Remove(current)) Console.WriteLine("Bad"); current = next; adjacent = edges[current]; } //When we get here, the view contains a closed path, i.e. //view[view.Count-1] == view[0] and we have followed all edges from view[0] //We slide forward along the rest of the path constructed so far and stop at the //first vertex with still unfollwed edges. while (view.Offset < path.Count - 1 && adjacent.Count == 0) { view.Slide(1, 1); if (debug) Console.WriteLine(" -> {0}, {1}", view[0], path.Count); adjacent = edges[view[0]]; } } if (path.Count <= edges.Count) throw new ArgumentException("Graph was not connected"); return path; } /// /// The purpose of this struct is to be able to create and add new, /// synthetic vertices to a graph. /// struct Vplus : IEquatable { internal readonly V vertex; internal readonly int id; static SCG.IEqualityComparer vequalityComparer = EqualityComparer.Default; internal Vplus(V v) { vertex = v; id = 0; } internal Vplus(int i) { vertex = default(V); id = i; } //We should override Equals and GetHashCode public override string ToString() { return id == 0 ? String.Format("real({0})", vertex) : String.Format("fake({0})", id); } public override bool Equals(object obj) { throw new NotImplementedException(); } public bool Equals(Vplus other) { return vequalityComparer.Equals(vertex, other.vertex) && id == other.id; } public override int GetHashCode() { return vequalityComparer.GetHashCode(vertex) + id; } } /// /// /// /// public IDirectedCollectionValue ApproximateTSP2() { /* Construct a minimum spanning tree for the graph */ V root; IGraph tree = MinimumSpanningTree(out root); //Console.WriteLine("========= Matching of odd vertices of mst ========="); ArrayList oddvertices = new ArrayList(); foreach (V v in tree.Vertices()) if (tree.Adjacent(v).Count % 2 != 0) oddvertices.Add(v); ICollectionValue> matching = SubGraph(oddvertices).ApproximateMWPM(); //Console.WriteLine("========= Fuse matching and tree ========="); //We must split the edges of the matching with fake temporary vertices //since the matching and the tree may have common edges HashGraph fused = new HashGraph(weight); foreach (Edge e in tree.Edges()) fused.AddEdge(new Vplus(e.start), new Vplus(e.end), e.edgedata); int fakeid = 1; foreach (Edge e in matching) { Vplus fakevertex = new Vplus(fakeid++); fused.AddEdge(new Vplus(e.start), fakevertex, e.edgedata); fused.AddEdge(fakevertex, new Vplus(e.end), e.edgedata); } fused.Print(Console.Out); //Console.WriteLine("========= Remove fake vertices and perform shortcuts ========="); IList fusedtour = fused.EulerTour(); HashSet seen = new HashSet(); IList tour = new ArrayList(); foreach (Vplus vplus in fused.EulerTour()) { V v = vplus.vertex; if (vplus.id == 0 && !seen.FindOrAdd(ref v)) tour.Add(v); } return tour; } /// /// (Refer to the litterature, Vazirani) /// /// (Describe: MST+Euler+shortcuts) /// /// [UsedBy("testTSP")] public IDirectedCollectionValue ApproximateTSP() { /* Construct a minimum spanning tree for the graph */ V root; IGraph tree = MinimumSpanningTree(out root); /* (Virtually) double all edges of MST and construct an Euler tour of the vertices*/ LinkedList tour = new LinkedList(); tour.Add(root); tour.Add(root); IList view = tour.View(1, 1); Action> onfollow = delegate(Edge e) { //slide the view until it points to the last copy of e.start while (!view[0].Equals(e.start)) view.Slide(1); //then insert two copies of e.end and slide the view one backwards view.InsertFirst(e.end); view.InsertFirst(e.end); view.Slide(1, 1); }; tree.TraverseVertices(false, root, onfollow); /* Finally, slide along the Euler tour and shortcut by removing vertices already seen*/ HashSet seen = new HashSet(); view = tour.View(0, tour.Count); while (view.Offset < tour.Count - 1) { V v = view[0]; if (seen.FindOrAdd(ref v)) view.RemoveFirst(); else view.Slide(1, view.Count - 1); } return tour; } public void Print(System.IO.TextWriter output) { output.WriteLine("Graph has {0} vertices, {1} edges, {2} components", graph.Count, edgecount, ComponentCount); foreach (KeyValuePair> p in graph) { output.Write(" {0} -> ", p.Key); foreach (KeyValuePair p2 in p.Value) output.Write("{1} (data {2}), ", p.Key, p2.Key, p2.Value); output.WriteLine(); } } } /// /// A weight on a graph that assigns the weight 1 to every edge. /// /// (Ignored) type of edgedata class CountWeight : IWeight { public int Weight(E edgedata) { return 1; } public int Add(int w1, int w2) { return w1 + w2; } } /// /// A weight on an IGraph<V,int> that uses the value of the edgedata as the weight value. /// class IntWeight : IWeight { public int Weight(int edgedata) { return edgedata; } public int Add(int w1, int w2) { return w1 + w2; } } /// /// A weight on an IGraph<V,double> that uses the value of the edgedata as the weight value. /// class DoubleWeight : IWeight { public double Weight(double edgedata) { return edgedata; } public double Add(double w1, double w2) { return w1 + w2; } } /// /// Attribute used for marking which examples use a particuler graph method /// class UsedByAttribute : Attribute { string[] tests; internal UsedByAttribute(params string[] tests) { this.tests = tests; } public override string ToString() { System.Text.StringBuilder sb = new System.Text.StringBuilder(); for (int i = 0; i < tests.Length; i++) { if (i > 0) sb.Append(", "); sb.Append(tests[i]); } return sb.ToString(); } } /// /// Attribute for marking example methods with a description /// class ExampleDescriptionAttribute : Attribute { string text; internal ExampleDescriptionAttribute(string text) { this.text = text; } public override string ToString() { return text; } } /// /// A selection of test cases /// class Test { static SCG.IEnumerable> Grid(int n) { Random ran = new Random(1717); for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { yield return new Edge(1000 * i + j, 1000 * (i + 1) + j, ran.Next(1, 100)); yield return new Edge(1000 * i + j, 1000 * i + j + 1, ran.Next(1, 100)); } } } static SCG.IEnumerable> Snake(int n) { for (int i = 1; i <= n; i++) { yield return new Edge("U" + i, "L" + i, 1); yield return new Edge("U" + i, "U" + (i + 1), i % 2 == 0 ? 1 : 10); yield return new Edge("L" + i, "L" + (i + 1), i % 2 == 0 ? 10 : 1); } } /// /// Create the edges of a forest of complete binary trees. /// /// Number of trees /// Height of trees /// static SCG.IEnumerable> Forest(int treeCount, int height) { for (int i = 0; i < treeCount; i++) { IList q = new ArrayList(); string root = String.Format("[{0}]", i); int lmax = height + root.Length; q.Add(root); while (!q.IsEmpty) { string s = q.Remove(); string s2 = s + "L"; if (s2.Length < lmax) q.Add(s2); yield return new Edge(s, s2, 0); s2 = s + "R"; if (s2.Length < lmax) q.Add(s2); yield return new Edge(s, s2, 0); } } } /// /// Create edges of a graph correspondingto a "wheel" shape: the ecnter and equidistant /// points around the perimeter. The edgedata and edge weights are the euclidean distances. /// /// True means the graph will be complete, false that the graph /// will have edges for the spokes and between neighboring perimeter vetices. /// The number of perimeter vertices, must be at least 3. /// An enumerable with the edges static SCG.IEnumerable> Wheel(bool complete, int n) { if (n < 3) throw new ArgumentOutOfRangeException("n must be at least 3"); string center = "C"; string[] perimeter = new string[n]; for (int i = 0; i < n; i++) { perimeter[i] = "P" + i; yield return new Edge(perimeter[i], center, 1); } if (complete) for (int i = 0; i < n - 1; i++) for (int j = i + 1; j < n; j++) yield return new Edge(perimeter[i], perimeter[j], 2 * Math.Sin((j - i) * Math.PI / n)); else { for (int i = 0; i < n - 1; i++) yield return new Edge(perimeter[i], perimeter[i + 1], 2 * Math.Sin(Math.PI / n)); yield return new Edge(perimeter[n - 1], perimeter[0], 2 * Math.Sin(Math.PI / n)); } } /// /// [] /// [ExampleDescription("Basic BFS and DFS using TraverseVertices method")] static void test1() { IGraph g = new HashGraph(new CountWeight(), Grid(3)); Console.WriteLine("Edge count: {0}", g.Edges().Count); Console.WriteLine("BFS:"); g.TraverseVertices(true, 1001, delegate(Edge e) { Console.WriteLine(e); }); Console.WriteLine("DFS:"); g.TraverseVertices(false, 1001, delegate(Edge e) { Console.WriteLine(e); }); } /// /// /// [ExampleDescription("Component methods")] static void testCOMP() { IGraph g = new HashGraph(new CountWeight(), Forest(2, 2)); Console.WriteLine("Forest has: Vertices: {0}, Edges: {1}, Components: {2}", g.VertexCount, g.EdgeCount, g.ComponentCount); //g.Print(Console.Out); foreach (KeyValuePair> comp in g.Components()) { Console.WriteLine("Component of {0}:", comp.Key); comp.Value.Print(Console.Out); } } //TODO: remove? static void test3() { HashGraph g = new HashGraph(new CountWeight(), Grid(5)); g.Print(Console.Out); //EdgeWeight weight = delegate(int i) { return i; }; Console.WriteLine("========= PFS accum ========="); g.PriorityFirstTraverse( true, 2002, delegate(Edge e, int d) { Console.WriteLine("Edge: {0}, at distance {1}", e, d); return true; }); Console.WriteLine("========= PFS not accum ========="); g.PriorityFirstTraverse( false, 2002, delegate(Edge e, int d) { Console.WriteLine("Edge: {0}, at distance {1}", e, d); return true; }); Console.WriteLine("========= MST ========="); int root; g.MinimumSpanningTree(out root).Print(Console.Out); Console.WriteLine("========= SP ========="); foreach (Edge edge in g.ShortestPath(1001, 5005)) Console.WriteLine(edge); } static void test4() { IGraph g = new HashGraph(new IntWeight(), Snake(5)); Console.WriteLine("Edge count: {0}", g.Edges().Count); Console.WriteLine("========= PFS ========="); g.PriorityFirstTraverse(false, "U3", delegate(Edge e, int d) { Console.WriteLine("Edge: {0}, at distance {1}", e, d); return true; }); Console.WriteLine("========= MST ========="); string root; IGraph mst = g.MinimumSpanningTree(out root); mst.Print(Console.Out); Console.WriteLine("DFS:"); mst.TraverseVertices(false, root, delegate(Edge e) { Console.WriteLine(e); }); Console.WriteLine("ATSP:"); int first = 0; foreach (string s in g.ApproximateTSP()) { Console.Write((first++ == 0 ? "" : " -> ") + s); } } /// /// This example examines the two variants of a priority-first search: /// with accumulated weights, leading to shortest paths from start; /// with non-acumulated weights, leading to a minimum spanning tree. /// [ExampleDescription("Priority-first-search with and without accumulated weights")] static void testPFS() { IGraph g = new HashGraph(new DoubleWeight(), Wheel(false, 10)); g.Print(Console.Out); Console.WriteLine("========= PFS non-accumulated weights (-> MST) ========="); g.PriorityFirstTraverse(false, "P0", delegate(Edge e, double d) { Console.WriteLine("Edge: {0}, at distance {1}", e, d); return true; }); Console.WriteLine("========= PFS accumulated weights (-> Shortst paths from start) ========="); g.PriorityFirstTraverse(true, "P0", delegate(Edge e, double d) { Console.WriteLine("Edge: {0}, at distance {1}", e, d); return true; }); } /// /// /// /// (Refer to Vazirani, or do that where ApproximateTSP is implemented) /// /// Note that the tour created is not optimal. [Describe why] /// [ExampleDescription("Approximate TSP")] static void testTSP() { IGraph g = new HashGraph(new DoubleWeight(), Wheel(true, 10)); //g.Print(Console.Out); Console.WriteLine("========= MST ========="); string root; IGraph mst = g.MinimumSpanningTree(out root); mst.TraverseVertices(false, root, delegate(Edge e) { Console.WriteLine("Edge: {0} -> {1}", e.start, e.end); }); Console.WriteLine("========= Approximate TSP ========="); int first = 0; foreach (string s in g.ApproximateTSP()) { Console.Write((first++ == 0 ? "" : " -> ") + s); } } /// /// /// /// (Refer to Vazirani, or do that where ApproximateTSP is implemented) /// /// Note that the tour created is not optimal. [Describe why] /// [ExampleDescription("Approximate TSP2")] static void testTSP2() { HashGraph g = new HashGraph(new DoubleWeight(), Wheel(true, 20)); foreach (string s in g.ApproximateTSP2()) Console.WriteLine("# " + s); //g.Print(Console.Out); /* Console.WriteLine("========= MST ========="); string root; IGraph mst = g.MinimumSpanningTree(out root); mst.TraverseVertices(false, root, delegate(Edge e) { Console.WriteLine("Edge: {0} -> {1}", e.start, e.end); }); ArrayList oddvertices = new ArrayList(); foreach (string v in mst.Vertices()) if (mst.Adjacent(v).Count % 2 != 0) oddvertices.Add(v); Console.WriteLine("========= Matching of odd vertices of mst ========="); ICollectionValue> matching = g.SubGraph(oddvertices).ApproximateMWPM(); Console.WriteLine("========= Add matching to mst ========="); //We must split the edges of the matchin with fake temporary vertices //(For a general vertex type, we would have to augment it to Pair int fake = 0; foreach (Edge e in matching) { string fakevertex = "_" + (fake++); mst.AddEdge(e.start, fakevertex, 0); mst.AddEdge(fakevertex, e.end, e.edgedata); } //mst.Print(Console.Out); IList tour = mst.EulerTour(), view = tour.View(1, tour.Count - 1); //Remove fake vertices while (view.Count > 0) if (view[0].StartsWith("_")) view.RemoveFirst(); else view.Slide(1, view.Count - 1); Console.WriteLine("========= Approximate TSP 2 ========="); //Short cut view = tour.View(1, tour.Count - 1); HashSet seen = new HashSet(); while (view.Count > 0) { string s = view[0]; if (seen.FindOrAdd(ref s)) view.RemoveFirst(); else view.Slide(1, view.Count - 1); } foreach (string s in tour) Console.WriteLine(". " + s);*/ } /// /// /// static void testEuler() { HashGraph g = new HashGraph(new DoubleWeight(), Wheel(true, 6)); foreach (string s in g.EulerTour()) Console.Write(s + " "); Console.WriteLine(); } /// /// An articulation point in a graph is a vertex, whose removal will /// disconnect the graph (or its component). This example uses the /// extended DepthFirstSearch method to compute articulation points /// of a graph. /// /// (Refer to Sedgewick. ) /// /// Each vertex is given an index in traversal order. /// For each vertex, we compute the least index reachable by going downwards /// in the DFS tree and then following a single non-dfs edge. /// /// Since we cannot store the DFS indices in the vertices without changing the /// (assumed given) vertex type, V, we remember the indices in a V->int /// hash dictionary, index. The "least reachable" values are then stored in an /// array keyed by the index. /// /// The root of the search is an articulation point if it has more than one /// outgoing DFS edges. Other articulation points are non-root vertices, va, /// with an outgoing DFS edge, where the the least reachable index of the other /// vertex is greater or equal to the index of va. /// [ExampleDescription("Using the advanced DFS to compute articulation points")] static void testDFS() { HashGraph g = new HashGraph(new IntWeight()); g.AddEdge("A", "B", 0); g.AddEdge("A", "E", 0); g.AddEdge("B", "E", 0); g.AddEdge("B", "C", 0); g.AddEdge("B", "H", 0); g.AddEdge("H", "I", 0); g.AddEdge("B", "D", 0); g.AddEdge("C", "D", 0); g.AddEdge("C", "F", 0); g.AddEdge("C", "G", 0); g.AddEdge("F", "G", 0); HashDictionary index = new HashDictionary(); int[] leastIndexReachableFrom = new int[g.VertexCount]; int nextindex = 0; int outgoingFromRoot = 0; Action beforevertex = delegate(string v) { int i = (index[v] = nextindex++); leastIndexReachableFrom[i] = i; }; Action aftervertex = delegate(string v) { int i = index[v]; if (i == 0 && outgoingFromRoot > 1) Console.WriteLine("Articulation point: {0} ({1}>1 outgoing DFS edges from start)", v, outgoingFromRoot); }; Action> onfollow = delegate(Edge e) { }; Action> onfollowed = delegate(Edge e) { int startind = index[e.start], endind = index[e.end]; if (startind == 0) outgoingFromRoot++; else { int leastIndexReachable = leastIndexReachableFrom[endind]; if (leastIndexReachable >= startind) Console.WriteLine("Articulation point: {0} (least index reachable via {3} is {1} >= this index {2})", e.start, leastIndexReachable, startind, e); if (leastIndexReachableFrom[startind] > leastIndexReachable) leastIndexReachableFrom[startind] = leastIndexReachable; } }; Action> onnotfollowed = delegate(Edge e) { int startind = index[e.start], endind = index[e.end]; if (leastIndexReachableFrom[startind] > endind) leastIndexReachableFrom[startind] = endind; }; string root = "C"; g.DepthFirstSearch(root, beforevertex, aftervertex, onfollow, onfollowed, onnotfollowed); Console.WriteLine("Edges:"); foreach (Edge e in g.Edges()) Console.WriteLine("/ {0}", e); } static void Main(String[] args) { if (args.Length != 1) { Console.WriteLine("Usage: Graph.exe testno"); System.Reflection.MethodInfo[] mis = typeof(Test).GetMethods( System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); foreach (System.Reflection.MethodInfo mi in mis) { if (mi.GetParameters().Length == 0 && mi.ReturnType == typeof(void) && mi.Name.StartsWith("test")) { object[] attrs = mi.GetCustomAttributes(typeof(ExampleDescriptionAttribute), false); Console.WriteLine(" {0} : {1}", mi.Name.Substring(4), attrs.Length > 0 ? attrs[0] : ""); } } } else { string testMethodName = String.Format("test{0}", args[0]); System.Reflection.MethodInfo mi = typeof(Test).GetMethod(testMethodName, System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); if (mi == null) Console.WriteLine("No such testmethod, {0}", testMethodName); else { object[] attrs = mi.GetCustomAttributes(typeof(ExampleDescriptionAttribute), false); Console.WriteLine("============================== {0}() ==================================", testMethodName); Console.WriteLine("Description: {0}", attrs.Length > 0 ? attrs[0] : "None"); Console.WriteLine("==========================================================================="); mi.Invoke(null, null); } } } } }