public void GetConnectionIndexBySourceNodeId() { var connArr = new DirectedConnection[9]; connArr[0] = new DirectedConnection(1, 70); connArr[1] = new DirectedConnection(4, 14); connArr[2] = new DirectedConnection(6, 23); connArr[3] = new DirectedConnection(7, 36); connArr[4] = new DirectedConnection(10, 18); connArr[5] = new DirectedConnection(10, 31); connArr[6] = new DirectedConnection(14, 2); connArr[7] = new DirectedConnection(20, 24); connArr[8] = new DirectedConnection(25, 63); int idx = DirectedConnectionUtils.GetConnectionIndexBySourceNodeId(connArr, 10); Assert.Equal(4, idx); idx = DirectedConnectionUtils.GetConnectionIndexBySourceNodeId(connArr, 25); Assert.Equal(8, idx); idx = DirectedConnectionUtils.GetConnectionIndexBySourceNodeId(connArr, 26); Assert.Equal(~9, idx); idx = DirectedConnectionUtils.GetConnectionIndexBySourceNodeId(connArr, 1); Assert.Equal(0, idx); idx = DirectedConnectionUtils.GetConnectionIndexBySourceNodeId(connArr, 0); Assert.Equal(~0, idx); }
private bool IsConnectionCyclicInner(IList <DirectedConnection> connList, DirectedConnection newConn) { // Test if the new connection is pointing to itself. if (newConn.SourceId == newConn.TargetId) { return(true); } // Note. We traverse forwards starting at the new connection's target node. If the new connection's source node is // encountered during traversal then the connection would form a cycle in the graph as a whole, and we return true. int startNodeId = newConn.TargetId; // Search for outgoing connections from the starting node. int connIdx = DirectedConnectionUtils.GetConnectionIndexBySourceNodeId(connList, startNodeId); if (connIdx < 0) { // The current node has no outgoing connections, therefore newConn does not form a cycle. return(false); } // Initialise and run the graph traversal algorithm. InitGraphTraversal(startNodeId, connIdx); // Note. we pass newConn.SourceId as the terminalNodeId; if traversal reaches this node then a cycle has been detected. return(TraverseGraph(connList, newConn.SourceId)); }
/// <summary> /// The graph traversal algorithm. /// </summary> /// <param name="connArr">A set of connections that represents the graph to traverse.</param> /// <param name="terminalNodeId">// The 'terminal' node ID, i.e. if traversal reaches this node then newConn would form a cycle and we stop/terminate traversal.</param> /// <returns></returns> private bool TraverseGraph(DirectedConnection[] connArr, int terminalNodeId) { // While there are entries on the stack. while (0 != _traversalStack.Count) { // Get the connection index from the top of stack; this is the next connection to be traversed. int currConnIdx = _traversalStack.Peek(); // Notes. // Before we traverse the current connection, update the stack state to point to the next connection // to be traversed from the current node. I.e. set up the stack state ready for when the traversal down // into the current connection completes and returns back to the current node. // // This is essentially tail call optimisation, and will result in shorter stacks on average and also // has the side effect that we can no longer examine the stack to observe the traversal path at a given // point in time. MoveForward(connArr, currConnIdx); // Test if the next traversal child node has already been visited. int childNodeId = connArr[currConnIdx].TargetId; int childNodeIdx = _nodeIdxByIdMap.Map(childNodeId); if (_visitedNodes[childNodeIdx]) { continue; } // Test if the connection target is the terminal node. if (childNodeId == terminalNodeId) { return(true); } // We're about to traverse into childNodeId, so mark it as visited to prevent re-traversal. _visitedNodes[childNodeIdx] = true; // Search for outgoing connections from childNodeId. // ENHANCEMENT: Eliminate binary search for childNodeId. int connIdx = DirectedConnectionUtils.GetConnectionIndexBySourceNodeId(connArr, childNodeId); if (connIdx >= 0) { // childNodeId has outgoing connections; push the first connection onto the stack to mark it for traversal. _traversalStack.Push(connIdx); } } // Traversal has completed without visiting the terminal node, therefore the new connection // does not form a cycle in the graph. return(false); }
/// <summary> /// The graph traversal algorithm. /// </summary> /// <param name="connList">A set of connections that represents the graph to traverse.</param> /// <param name="terminalNodeId">// The 'terminal' node ID, i.e. if traversal reaches this node then newConn would form a cycle and we stop/terminate traversal.</param> /// <returns></returns> private bool TraverseGraph(IList <DirectedConnection> connList, int terminalNodeId) { // While there are entries on the stack. while (0 != _traversalStack.Count) { // Get the connection index from the top of stack; this is the next connection to be traversed. int currConnIdx = _traversalStack.Peek(); // Notes. // Before we traverse the current connection, update the stack state to point to the next connection to be // traversed, either from the current node or a parent node. I.e. we modify the stack state ready for when // the traversal down into the current connection completes and returns back to the current node. // // This approach results in tail call optimisation and thus will result in a shallower stack on average. It // also has the side effect that we can no longer examine the stack to observe the traversal path at a given // point in time, since some of the path may no longer be on the stack. MoveForward(connList, currConnIdx); // Test if the next traversal child node has already been visited. int childNodeId = connList[currConnIdx].TargetId; if (_visitedNodes.Contains(childNodeId)) { continue; } // Test if the connection target is the terminal node. if (childNodeId == terminalNodeId) { return(true); } // We're about to traverse into childNodeId, so mark it as visited to prevent re-traversal. _visitedNodes.Add(childNodeId); // Search for outgoing connections from childNodeId. int connIdx = DirectedConnectionUtils.GetConnectionIndexBySourceNodeId(connList, childNodeId); if (connIdx >= 0) { // childNodeId has outgoing connections; push the first connection onto the stack to mark it for traversal. _traversalStack.Push(connIdx); } } // Traversal has completed without visiting the terminal node, therefore the new connection // does not form a cycle in the graph. return(false); }