/// <summary> /// Retrieves the path distance from one node to another, or calculates /// it if it has not yet been found and CalculateFully has not been called. /// </summary> /// <param name="a">The first graph node.</param> /// <param name="b">The second graph node.</param> /// <returns>The length of the path from a to b (equals the amount of edges /// traversed).</returns> /// <remarks> /// If CalculateFully has been called and the nodes are not connected, 0 will be returned. /// If CalculateFully has been called and the nodes were not both passed to it, a IndexOutOfRangeException will be thrown. /// If CalculateFully has not been called and the nodes are not connected, a GraphNotConnectedException will be thrown. /// </remarks> public int this[GraphNode a, GraphNode b] { get { if (FullyCached) { return _distancesFast[a.DistancesIndex, b.DistancesIndex]; } var index = GetIndex(a, b); if (!_distances.ContainsKey(index)) { Dijkstra(a, b); } return _distances[index]; } }
public GraphEdge(GraphNode inside, GraphNode outside) { Inside = inside; Outside = outside; }
/// <summary> /// Adds the distance and shortest path between from and to to the respectives /// dictionarys if not already present. /// </summary> private void AddEdge(GraphNode from, GraphNode to, int distFromStart, IDictionary<ushort, ushort> predecessors) { var length = distFromStart + 1; if (FullyCached) { var i1 = from.DistancesIndex; var i2 = to.DistancesIndex; if (_pathsFast[i1, i2] != null) return; var path = length > 0 ? GenerateShortestPath(from.Id, to.Id, predecessors, length) : new ushort[0]; _distancesFast[i1, i2] = _distancesFast[i2, i1] = length; _pathsFast[i1, i2] = _pathsFast[i2, i1] = path; } else { var index = GetIndex(from, to); if (_distances.ContainsKey(index)) return; var path = length > 0 ? GenerateShortestPath(from.Id, to.Id, predecessors, length) : new ushort[0]; _paths[index] = path; _distances[index] = length; } }
/// <summary> /// Uses a djikstra-like algorithm to flood the graph from the start /// node until the target node is found (if specified) or until all marked nodes got checked. /// </summary> /// <param name="start">The starting node. (not null)</param> /// <param name="target">The (optional) target node.</param> /// <exception cref="GraphNotConnectedException"> /// If target node is not null and it could not be found. /// </exception> private void Dijkstra(GraphNode start, GraphNode target = null) { if (start == null) throw new ArgumentNullException("start"); AddEdge(start, start, -1, null); if (start == target) return; // The last newly found nodes. var front = new HashSet<GraphNode>() { start }; // The already visited nodes. var visited = new HashSet<GraphNode>() { start }; // The dictionary of the predecessors of the visited nodes. var predecessors = new Dictionary<ushort, ushort>(); // The traversed distance from the starting node in edges. var distFromStart = 0; while (front.Count > 0) { var newFront = new HashSet<GraphNode>(); foreach (var node in front) { foreach (var adjacentNode in node.Adjacent) { if (visited.Contains(adjacentNode)) continue; predecessors[adjacentNode.Id] = node.Id; if (adjacentNode == target) { AddEdge(start, adjacentNode, distFromStart, predecessors); return; } if (adjacentNode.DistancesIndex >= 0) { AddEdge(start, adjacentNode, distFromStart, predecessors); } newFront.Add(adjacentNode); visited.Add(adjacentNode); } } front = newFront; distFromStart++; } // Target node was not found because start and target are not connected. if (target != null) throw new GraphNotConnectedException(); }
/// <summary> /// Compounds two ushort node indices into a single uint one, which /// is independent of the order of the two indices. /// </summary> /// <param name="a">The first index.</param> /// <param name="b">The second index.</param> /// <returns>The compounded index.</returns> private static uint GetIndex(GraphNode a, GraphNode b) { var aId = a.Id; var bId = b.Id; return (uint)(Math.Min(aId, bId) << 16) + Math.Max(aId, bId); }
/// <summary> /// Returns whether the given nodes are connected. /// </summary> public bool AreConnected(GraphNode a, GraphNode b) { try { // Null if not connected and _fullyCached // Exception if not connected and not _fullyCached return GetShortestPath(a, b) != null; } catch (GraphNotConnectedException) { return false; } }
/// <summary> /// Retrieves the shortest path from one node to another, or calculates /// it if it has not yet been found and CalculateFully has not been called. /// </summary> /// <param name="a">The first graph node. (not null)</param> /// <param name="b">The second graph node. (not null)</param> /// <returns>The shortest path from a to b, not containing either and ordered from a to b or b to a.</returns> /// <remarks> /// If CalculateFully has been called and the nodes are not connected, null will be returned. /// If CalculateFully has been called and the nodes were not both passed to it, a IndexOutOfRangeException will be thrown. /// If CalculateFully has not been called and the nodes are not connected, a GraphNotConnectedException will be thrown. /// </remarks> public ushort[] GetShortestPath(GraphNode a, GraphNode b) { if (FullyCached) { return _pathsFast[a.DistancesIndex, b.DistancesIndex]; } var index = GetIndex(a, b); if (!_distances.ContainsKey(index)) { Dijkstra(a, b); } return _paths[index]; }