public void TestDijkstra() { // TODO: Maybe make the graphs class members. /// 0 -- 1 -- 2 -- 3 /// \ | / /// \ | / /// 4 -- 5 bool[,] graph1 = { { false, true, false, false, true, false }, { true, false, true, false, false, false }, { false, true, false, true, false, true }, { false, false, true, false, false, true }, { true, false, false, false, false, true }, { false, false, true, true, true, false }, }; SearchGraph searchGraph1 = SearchGraphFromData(graph1); Dictionary<int, GraphNode> graphNodes = GetGraphNodesIdIndex(searchGraph1); DistanceLookup distanceLookup = new DistanceLookup(); Assert.IsTrue(distanceLookup.GetDistance(graphNodes[0], graphNodes[0]) == 0, "Failed 0 distance test"); Assert.IsTrue(distanceLookup.GetDistance(graphNodes[0], graphNodes[5]) == 2, "Wrong distance"); Assert.IsTrue(distanceLookup.GetDistance(graphNodes[0], graphNodes[3]) == 3, "Wrong distance"); }
/// <summary> /// Finds the nodes in the search graph that can be potential steiner /// nodes. Those form the search space base. /// </summary> private void buildSearchSpaceBase() { searchSpaceBase = new List <GraphNode>(); MinimalSpanningTree leastSolution = new MinimalSpanningTree(targetNodes, distances); leastSolution.Span(startFrom: startNodes); int maxEdgeDistance = 0; foreach (GraphEdge edge in leastSolution.SpanningEdges) { int edgeDistance = distances.GetDistance(edge); if (edgeDistance > maxEdgeDistance) { maxEdgeDistance = edgeDistance; } } /* * int maxTargetDistance = 0; * foreach (GraphNode targetNode in targetNodes) * { * int targetDistance = distances.GetDistance(targetNode, startNodes); * if (targetDistance > maxTargetDistance) * maxTargetDistance = targetDistance; * }*/ // Find potential steiner points that are in reasonable vicinity. /// TODO: This can surely be improved in some shape or form, but I /// can't figure it out right now. Since the GA also has to work well /// with larger input sizes, I won't investigate this right now. foreach (GraphNode node in searchGraph.nodeDict.Values) { // This can be a steiner node only if it has more than 2 neighbors. if (node.Adjacent.Count > 2) { /* * While this would mathematically be correct, it's not a * good criterium for the skill tree. I don't think the * relevant cases can appear and this permits way too many * nodes to be considered that will never be included in an * actual solution. * * /// If every target node is closer to the start than to a certain * /// steiner node, that node can't be needed for the steiner tree. * bool add = false; * foreach (GraphNode targetNode in targetNodes) * if (distances.GetDistance(targetNode, node) < distances.GetDistance(targetNode, startNodes)) * add = true; * if (add) * searchSpaceBase.Add(node); */ /* * /// This is a pretty handwavy approach... If anybody figures * /// out a case that causes this to fail, let me know please! * if (distances.GetDistance(node, startNodes) < 1.2 * maxTargetDistance) * searchSpaceBase.Add(node); */ // This should be a reasonable approach. bool add = false; foreach (GraphNode targetNode in targetNodes) { if (distances.GetDistance(targetNode, node) < maxEdgeDistance) { add = true; } } if (add) { searchSpaceBase.Add(node); } } } /* ONLY FOR WHEN NODES HAVE INDIVIDUAL WEIGHTS * foreach (ushort nodeId in targetSkillnodes) * { * searchSpaceBase.Add(SkillTree.Skillnodes[nodeId]); * }*/ }
/// <summary> /// Uses Prim's algorithm to build an MST spanning the mstNodes. /// </summary> /// <param name="startFrom">A GraphNode to start from.</param> /// <returns>A list of GraphEdges forming the MST.</returns> public List <GraphEdge> Span(GraphNode startFrom) { /// With n nodes, we can have up to n (actually n-1) edges adjacent to each node. HeapPriorityQueue <GraphEdge> adjacentEdgeQueue = new HeapPriorityQueue <GraphEdge>(mstNodes.Count * mstNodes.Count); /// Removing all edges that satisfy a property (here a certain "outside" /// node) from the queue is not actually trivial, since you could only /// iterate over all entries (and you want to avoid that) if you don't /// have the references to the edges at hand. /// I guess this is the easiest way to do it... Dictionary <GraphNode, List <GraphEdge> > edgesLeadingToNode = new Dictionary <GraphNode, List <GraphEdge> >(); foreach (GraphNode node in mstNodes) { edgesLeadingToNode[node] = new List <GraphEdge>(); } // All nodes that are already included. HashSet <GraphNode> inMst = new HashSet <GraphNode>(); // All nodes that are not yet included. HashSet <GraphNode> toAdd = new HashSet <GraphNode>(mstNodes); List <GraphEdge> mstEdges = new List <GraphEdge>(); // Initialize the MST with the start nodes. inMst.Add(startFrom); toAdd.Remove(startFrom); edgesLeadingToNode[startFrom] = new List <GraphEdge>(); foreach (GraphNode otherNode in toAdd) { GraphEdge adjacentEdge = new GraphEdge(startFrom, otherNode); adjacentEdgeQueue.Enqueue(adjacentEdge, distances.GetDistance(adjacentEdge)); edgesLeadingToNode[otherNode].Add(adjacentEdge); } while (toAdd.Count > 0 && adjacentEdgeQueue.Count > 0) { GraphEdge shortestEdge = adjacentEdgeQueue.Dequeue(); mstEdges.Add(shortestEdge); GraphNode newIn = shortestEdge.outside; //if (inMst.Contains(newIn)) throw new Exception(); //if (!toAdd.Contains(newIn)) throw new Exception("No edge to this node should remain!"); inMst.Add(newIn); toAdd.Remove(newIn); // Remove all edges that are entirely inside the MST now. foreach (GraphEdge obsoleteEdge in edgesLeadingToNode[newIn]) { //if (!inMst.Contains(obsoleteEdge.inside)) throw new Exception("This edge's inside node is not inside"); adjacentEdgeQueue.Remove(obsoleteEdge); } edgesLeadingToNode.Remove(newIn); // Find all newly adjacent edges and enqueue them. foreach (GraphNode otherNode in toAdd) { GraphEdge adjacentEdge = new GraphEdge(newIn, otherNode); adjacentEdgeQueue.Enqueue(adjacentEdge, distances.GetDistance(adjacentEdge)); edgesLeadingToNode[otherNode].Add(adjacentEdge); } } if (toAdd.Count > 0) { throw new DistanceLookup.GraphNotConnectedException(); } this.SpanningEdges = mstEdges; _isSpanned = true; return(mstEdges); }