JS Implementation Judges Whether DAG Graph Has a Ring

Keywords: Javascript

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...

Posted by php_newB on Fri, 30 Aug 2019 03:08:04 -0700