/// <summary> /// Merges the node x into the fixed target node into. /// /// Edges between these nodes, edges to x and related distance information is updated. /// /// x is marked as to be removed from the search space. If x was the start node, into /// will now be the start node. /// </summary> /// <returns>All neighbors of x before merging. These are the nodes that had their adjacency /// information changed.</returns> protected IEnumerable <int> MergeInto(int x, int into) { if (!NodeStates.IsFixedTarget(into)) { throw new ArgumentException("Nodes can only be merged into fixed target nodes", "into"); } _data.DistanceLookup.IndexToNode(into).MergeWith(_data.DistanceLookup.IndexToNode(x), _data.DistanceLookup.GetShortestPath(x, into)); _data.DistanceLookup.MergeInto(x, into); EdgeSet.Remove(x, into); var intoNeighbors = EdgeSet.NeighborsOf(into); var xNeighbors = EdgeSet.NeighborsOf(x); var neighbors = intoNeighbors.Union(xNeighbors); foreach (var neighbor in xNeighbors) { EdgeSet.Remove(x, neighbor); } foreach (var neighbor in neighbors) { EdgeSet.Add(into, neighbor, _data.DistanceLookup[into, neighbor]); } if (StartNodeIndex == x) { _data.StartNodeIndex = into; } NodeStates.MarkNodeAsRemoved(x); return(xNeighbors); }
/// <summary> /// Creates a new object that can reduce the problem instance described by the given parameters. /// </summary> /// <param name="searchSpace">Problem solutions must be a subset of these nodes. They also describe the initial edges. /// Must contain all nodes from the other parameters.</param> /// <param name="fixedTargetNodes">All nodes that must be included in solutions. The nodes for which the minimal /// Steiner tree has to be found.</param> /// <param name="startNode">The node from which solution trees are built. Must be a fixed target node. /// If null, the first fixed target node will be taken.</param> /// <param name="variableTargetNodes">Finding the subset of these nodes (unioned with the fixed target nodes) whose /// minimal Steiner tree optimizes the constraints formulates the extended Steiner problem solved by the AdvancedSolver. /// These nodes are in a special state between normal nodes and fixed target nodes for reductions. /// If null, no nodes are considered as variable target nodes and the problem is an instance of the normal Steiner tree problem.</param> /// <remarks> /// The fixed target nodes are removed from the search space and then added to the end of it so they always get the last distance /// indices. This improves the quality of solutions because the MST algorithm considers edges involving these after same priority edges /// not involving them. /// </remarks> public SteinerPreprocessor(IEnumerable <GraphNode> searchSpace, IEnumerable <GraphNode> fixedTargetNodes, GraphNode startNode = null, IEnumerable <GraphNode> variableTargetNodes = null) { var fixedTargetNodesSet = new HashSet <GraphNode>(fixedTargetNodes); if (!fixedTargetNodesSet.Any()) { throw new ArgumentException("At least one fixed target node must be provided.", "fixedTargetNodes"); } // Fixed target nodes are added last into the search space to get the last distance indices. _nodeStates = new NodeStates(searchSpace.Except(fixedTargetNodesSet).Union(fixedTargetNodesSet), fixedTargetNodesSet, variableTargetNodes ?? new GraphNode[0]); _data = new Data(startNode ?? _nodeStates.FixedTargetNodes.First()); if (startNode != null && !_nodeStates.IsFixedTarget(startNode)) { throw new ArgumentException("Start node must be a fixed target node if specified.", "startNode"); } }
protected override int ExecuteTest() { var removedNodes = 0; var untested = new HashSet <int>(Enumerable.Range(0, SearchSpaceSize)); var dependentNodes = new Dictionary <int, List <int> >(); while (untested.Any()) { var i = untested.First(); untested.Remove(i); var neighbors = EdgeSet.NeighborsOf(i); if (!NodeStates.IsTarget(i)) { if (neighbors.Count == 1) { // Non target nodes with one neighbor can be removed. untested.Add(neighbors[0]); RemoveNode(i); removedNodes++; } else if (neighbors.Count == 2) { // Non target nodes with two neighbors can be removed and their neighbors // connected directly. untested.Add(neighbors[0]); untested.Add(neighbors[1]); RemoveNode(i); removedNodes++; } } else if (NodeStates.IsFixedTarget(i)) { if (neighbors.Count == 1) { var other = neighbors[0]; if (EdgeSet.NeighborsOf(other).Count > 2 || NodeStates.IsTarget(other)) { // Fixed target nodes with one neighbor can be merged with their neighbor since // it must always be taken. untested.Add(i); untested.Remove(other); untested.UnionWith(MergeInto(other, i)); removedNodes++; } else { // Node can only be merged once other has been processed. Other might be a dead end. Debug.Assert(untested.Contains(other)); dependentNodes.Add(other, i); } } else if (neighbors.Count > 1) { // Edges from one target node to another that are of minimum cost among the edges of // one of the nodes can be in any optimal solution. Therefore both target nodes can be merged. var minimumEdgeCost = neighbors.Min(other => DistanceLookup[i, other]); var minimumTargetNeighbors = neighbors.Where(other => DistanceLookup[i, other] == minimumEdgeCost && NodeStates.IsFixedTarget(other)); foreach (var other in minimumTargetNeighbors) { untested.Add(i); untested.Remove(other); untested.UnionWith(MergeInto(other, i)); removedNodes++; } } } List <int> dependent; if (dependentNodes.TryGetValue(i, out dependent)) { untested.UnionWith(dependent); } } return(removedNodes); }
protected override int ExecuteTest() { var removedNodes = 0; var untested = new HashSet <int>(Enumerable.Range(0, SearchSpaceSize)); while (untested.Any()) { var i = untested.First(); untested.Remove(i); var neighbors = EdgeSet.NeighborsOf(i); if (!NodeStates.IsTarget(i)) { if (neighbors.Count == 1) { // Non target nodes with one neighbor can be removed. untested.Add(neighbors[0]); RemoveNode(i); removedNodes++; } else if (neighbors.Count == 2) { // Non target nodes with two neighbors can be removed and their neighbors // connected directly. untested.Add(neighbors[0]); untested.Add(neighbors[1]); RemoveNode(i); removedNodes++; } } else if (NodeStates.IsFixedTarget(i)) { if (neighbors.Count == 1) { var other = neighbors[0]; // Fixed target nodes with one neighbor can be merged with their neighbor since // it must always be taken. // If the neighbor is no target and of degree 1 or 2, it will be removed by the tests above, // after which this node will be processed again. if (EdgeSet.NeighborsOf(other).Count <= 2 && !NodeStates.IsTarget(other)) { continue; } // If this node is the only fixed target, the neighbor must not be merged because // if this is the only target, the neighbor and the rest of the tree will not be in the optimal solution, // if there are only variable target nodes, the neighbor will not be in the optimal solution if the variable nodes are not taken. if (NodeStates.FixedTargetNodeCount == 1) { continue; } untested.Add(i); untested.Remove(other); untested.UnionWith(MergeInto(other, i)); removedNodes++; } else if (neighbors.Count > 1) { // Edges from one target node to another that are of minimum cost among the edges of // one of the nodes can be in any optimal solution. Therefore both target nodes can be merged. var minimumEdgeCost = neighbors.Min(other => DistanceLookup[i, other]); var minimumTargetNeighbors = neighbors.Where(other => DistanceLookup[i, other] == minimumEdgeCost && NodeStates.IsFixedTarget(other)); foreach (var other in minimumTargetNeighbors) { untested.Add(i); untested.Remove(other); untested.UnionWith(MergeInto(other, i)); removedNodes++; } } } } return(removedNodes); }