Business scenarios
The task visualization interface of dispatching system needs to complete the user's connection on the interface as a dependency relationship between any two job s, that is, DAG graph.
- DAG is a directed acyclic graph. A directed acyclic graph is a directed graph without loop. A ring is a path that contains at least one edge and has the same starting and ending points.
Problem Description
When adding dependencies, before sending requests to the back end, the front end should first determine whether the currently added connections and the existing dependencies become closed-loop (circular dependencies are invalid task flows) to reduce invalid requests.
Jobs can be arbitrarily dependent, that is, each job can have multiple byte points or parent nodes.
Understanding of Rings
At first, it was thought that the loops visible to the naked eye were rings. The following figure finally converged to Wang Xiaohu 4, but from the dependency relation of digraph, it is actually 1 dependence 4, 1 dependence 2, 2 dependence 3, 3 dependence 4, just waiting for the execution of node 4, and not falling into the dependence of dead cycle. But the next one, if it's an undirected graph, is a ring.
- Directed graphs and undirected graphs: From the graph, it is easy to understand that arrows point to ring graphs and arrows point to acyclic graphs.
Judging whether a digraph has a ring
There are two algorithms:
1. Depth-first traversal of the graph, if in the process of traversal, it is found that a node has an edge pointing to the node that has been visited, then it is judged to have a ring.
//Deep ergodic functions of Graphs function DFS(i) { console.log('Accessing Node' + nodes[i]); //Node i becomes an accessed state visited[nodes[i]] = 1; for (let j = 0; j < nodes.length; j++) { //If the current node has a pointed node if (graph[nodes[i]][nodes[j]] != 0) { //And it's been visited. if (visited[nodes[j]] == 1) { isDAG = false; //Ring break; } else if (visited[nodes[j]] == -1) { //The node behind the current node has been visited and jumped directly to the next node. continue; } else { DFS(j); //Otherwise, recursive access } } } //After traversing all the connected nodes, mark the node as -1 visited[nodes[i]] = -1; } //Create a graph represented by an adjacency matrix function create(nodes, edges) { for (let i = 0; i < nodes.length; i++) { const pre = nodes[i]; graph[pre] = {}; for (let j = 0; j < nodes.length; j++) { const next = nodes[j]; graph[pre][next] = 0; } } for (let k = 0; k < edges.length; k++) { const edge = edges[k]; graph[edge.source][edge.target] = 1; } //Initialize the color array to 0, meaning that all vertices have not been accessed at first for (let i = 0; i < nodes.length; i++) { visited[nodes[i]] = 0; } } //adjacency matrix const graph = {}; //Number of nodes and edges //Markup matrix, 0 is the current node is not visited, 1 is visited, and - 1 means that the nodes behind the current node have been visited. const visited = {}; //Is it DAG (Directed Acyclic Graph) let isDAG = true; // Get all the nodes const edges = [ { source: 'node1', target: 'node3' }, { source: 'node3', target: 'node5' } ]; const nodes = []; edges.forEach(e => { const { source, target } = e; if (!nodes.includes(source)) { nodes.push(source); } if (!nodes.includes(target)) { nodes.push(target); } }); create(nodes, edges); //Ensure that every node traverses, excluding some nodes without edges for (let i = 0; i < nodes.length; i++) { //The nodes behind the node have been visited, skip it if (visited[nodes[i]] == -1) { continue; } DFS(i); if (!isDAG) { console.log('Ring'); break; } } if (isDAG) { console.log('exannulate'); }
- Because the number of matrix elements is n^2, the time complexity of this algorithm is O(n^2).
2. Topological Sorting
- Find a node without a precursor (entry is 0). Initiation refers to the dependency of the node. Delete the node and all directed edges starting from it from the graph.
- In the process of deleting the directed edge of the node, the node entry is updated and the next node with zero entry is found until the current DAG graph is empty or there is no Vertex without precursor in the current graph, otherwise there is a loop.
// Get all the nodes const edges = [ { source: 'node1', target: 'node3' }, { source: 'node3', target: 'node4' }, { source: 'node1', target: 'node4' } ]; const nodes = []; const list = {}; // Adjacency table const queue = []; // Node Set with Degree 0 const indegree = {}; edges.forEach(e => { const { source, target } = e; if (!nodes.includes(source)) { nodes.push(source); } if (!nodes.includes(target)) { nodes.push(target); } addEdge(source, target); }); const V = nodes.length; nodes.forEach(node => { if (!indegree[node]) indegree[node] = 0; if (!list[node]) list[node] = []; }); function addEdge(source, target) { if (!list[source]) list[source] = []; if (!indegree[target]) indegree[target] = 0; list[source].push(target); indegree[target] += 1; } function sort() { Object.keys(indegree).forEach(id => { if (indegree[id] === 0) { queue.push(id); } }); let count = 0; while (queue.length) { ++count; const currentNode = queue.pop(); const nodeTargets = list[currentNode]; for (let i = 0; i < nodeTargets.length; i++) { const target = nodeTargets[i]; indegree[target] -= 1; if (indegree[target] === 0) { queue.push(target); } } } // false does not output all vertices and has a loop in the digraph return !(count < V); } console.log(sort());
- Because each vertex is output and the edge starting from it is deleted, the time complexity of the above topological sorting is O(V+E).
Extension: Judging whether an undirected graph has a ring
1. Traverse all the current dependency edges and add all the dependency nodes of the joint points to the adjacent table, where an array is used to store them.
2. Assuming that the starting point is a and the ending point is b, the adjacent dependencies of the current graph a node are traversed along the a node and no point with the end point is found, then the dependency is also acyclic.
3. In order to prevent falling into a dead loop, a visited array is added to the side single loop to indicate that the node has been visited.
const edges = [ { source: 1, target: 3 }, { source: 3, target: 5 }, { source: 2, target: 3 }, { source: 4, target: 1 }, { source: 1, target: 6 }, { target: 5, source: 6 } ]; function findRedundantConnection(edges) { // Storing graph information using adjacency tables const graph = new Map(); // Traveling through each side for (let i = 0; i < edges.length; i++) { const edge = edges[i]; // Each element of edges is a pair [u, v] with u < v // const u = edge[0]; // const v = edge[1]; const u = edge.source; const v = edge.target; // Depth first traverses the graph to determine whether a path exists between u and v let hasPath = dfs(graph, u, v, []); if (hasPath == true) { return edge; } else { // Add this edge to the adjacency table if (!graph.has(u)) { graph.set(u, []); } const uArr = graph.get(u); uArr.push(v); graph.set(u, uArr); if (!graph.has(v)) { graph.set(v, []); } const vArr = graph.get(v); vArr.push(u); graph.set(v, vArr); } } return null; } // Depth first traverses the graph to determine whether a path exists between start and end function dfs(graph, start, end, visited) { if (!graph.has(start) || !graph.has(end)) { return false; } if (start == end) { return true; } visited.push(start); // Traversing all adjacent nodes of start const startArr = graph.get(start); for (let i = 0; i < startArr.length; i++) { const adj = startArr[i]; if (!visited.includes(adj)) { if (dfs(graph, adj, end, visited) == true) { return true; } } } return false; } console.log(findRedundantConnection(edges));
Reference link:
https://blog.csdn.net/lisongl...
https://blog.csdn.net/login_s...