Graph theory -- ring determination and matrix algorithm

Graph is an important part of classical computer algorithm. It has a wide range of applications from Internet structure to power topology, from economic market model to medical infection prediction of infectious diseases. Graph research can be divided into connectivity, path problem, reachability and so on. Today, we only focus on the ring determination of digraphs and undirected graphs. First, we use Java language to implement their basic algorithms, and then I will show you how to use the matrix to assist the algorithm through the mathematical model.
The data structure composed of a series of connected nodes and is called graph. Whether as a social network or a map, computers may encounter various mathematical problems when dealing with such problems. We will learn two important graph models: undirected graph (simple join) and directed graph (join oriented).
The structure of a graph may be very complex, but in order to simplify our research, we only involve the following two basic structures. In Figure 1, figure 2 only adjusts 2 and 3 nodes, and figure 3 represents a digraph.

1, Undirected graph and ring determination

In the graph model we first need to learn, edge is only the connection between two vertices. In undirected graph, if there is a connection between nodes 1 and 2, it means that 0-1 and 1-0 exist at the same time. First, we need to define objects that represent the structure of undirected graphs:

import java.util.LinkedList;
import java.util.List;

public class Graph {
    private int v; // Total number of vertices
    private int e; // Total number of sides
    private List<Integer>[] adj; // Adjacency table array

    public Graph(int v) {
        this.v = v;
        this.e = 0;
        adj = (List<Integer>[]) new List[v];
        for (int i = 0; i < v; i++) {
            adj[i] = new LinkedList<>();
        }
    }

    /**
     * Since parallel edges are allowed in the graph, it is not excluded to save the same key in the adjacency table
     *
     * @param v fixed point
     * @param w fixed point
     */
    public void addEdge(int v, int w) {
        adj[v].add(w);
        adj[w].add(v);
        e++;
    }

    public void delEdge(int v, int w) {
        if (adj[v].contains(w)) {
            adj[v].remove(w);
            adj[w].remove(v);
        }
    }

    public boolean checkEdge(int v, int w) {
        return adj[v].contains(w);
    }

    public Iterable<Integer> adj(int v) {
            return adj[v];
    }

    public int degree(int v) {
        return adj[v].size();
    }

    public int V() {
        return v;
    }

    public int E() {
        return e;
    }
}

To detect the existence of rings in a undirected graph, we need to use an algorithm called depth first search (dfs). The logic of this algorithm is that we start from the origin (s) and continue to search downward from one connection. If the next node has no subsequent nodes, it will return to the original path and continue from other connections until all the nodes in the graph are traversed. Every time we traverse to a node, we will make a mark. When we find that the subsequent nodes of a node have been marked, it means that there is a ring in the graph. The algorithm is as follows:

/**
 * Application of depth first algorithm: ring detection
 */
public class Cycle {
    private boolean[] marked;
    private boolean isCycle;

    public Cycle(Graph g) {
        marked = new boolean[g.V()];
        for (int s = 0; s < g.V(); s++) {
            if (!marked[s]) {
                dfs(g, s, s);
            }
        }
    }

    /**
     * The nodes of acyclic graph are detected by depth first algorithm. The marked nodes w must be equal to the former nodes u (v)
     *
     * @param g
     * @param v
     * @param u
     */
    public void dfs(Graph g, int v, int u) {
        marked[v] = true;
        for (int w : g.adj(v)) {
            if (!marked[w]) {
                dfs(g, w, v);
            } else if (w != u) {
                this.isCycle = true;
                return;
            }
        }
    }

    public boolean isCycle() {
        return isCycle;
    }

    public static void main(String[] args) {
        Graph g = new Graph(3);
        g.addEdge(0, 1);
        g.addEdge(1, 2);
        g.addEdge(0, 2);
        Cycle c = new Cycle(g);
        System.out.println(c.isCycle());
    }
}

This algorithm is not complicated, but if you are not familiar with it, the best way is to go deep into every step of recursion and fully understand the above explanation. Please note that depth first algorithm (dfs) is the basic idea of graph theory algorithm to solve many problems, so you should master it.

2, Digraph and ring determination

In a directed graph, edges are unidirectional: the two vertices connected by each edge are an ordered pair, and their adjacency is unidirectional. The data objects of the digraph are as follows:

import java.util.ArrayList;
import java.util.List;

public class Digraph {
    private int v;
    private int e;
    private List<Integer>[] adj;

    public Digraph(int v) {
        this.v = v;
        adj = (List<Integer>[]) new List[v];
        for(int i = 0; i < v; i++) {
            adj[i] = new ArrayList<Integer>();
        }
    }

    public void addEdge(int v, int w) {
        adj[v].add(w);
        e++;
    }

    public Iterable<Integer> adj(int v) {
        return adj[v];
    }

    public Digraph reverse() {
        Digraph r = new Digraph(v);
        for (int i = 0; i < v; i++) {
            for (int w : adj(v)) {
                r.addEdge(w, v); // Node direction conversion
            }
        }
        return r;
    }

    public int V() {
        return v;
    }

    public int E() {
        return e;
    }

}

Although directed graph is more complex than undirected graph, it is still suitable for ring detection. We will still use dfs algorithm to mark the path. And look at each successor node. The difference is that even if we find that the subsequent nodes have been labeled in the previous traversal, we need another flag to ensure that these nodes are in the same path. For example, there is no directed ring in the figure below:

In geometry, the relationship between vector 4(V4) and vectors 2(V2) and 3(V3): V4 = V2 + V3. Obviously, the ring only holds when: V4 + V2 + V3 = 0. The algorithm of directed ring is as follows:

import java.util.Stack;

/**
 * Searching for directed rings
 */
public class DirectedCycle {
    private boolean[] marked;
    private int[] edgeTo;
    private Stack<Integer> cycle;
    private boolean[] onStack;

    public DirectedCycle(Digraph g) {
        onStack = new boolean[g.V()];
        edgeTo = new int[g.V()];
        marked = new boolean[g.V()];
        for (int v = 0; v < g.V(); v++) {
            if (!marked[v]) {
                dfs(g, v);
            }
        }
    }

    /**
     * Add a ring tag every time you enter recursion, and cancel the ring tag when you return recursion
     *
     * @param g
     * @param v
     */
    private void dfs(Digraph g, int v) {
        onStack[v] = true;
        marked[v] = true;
        for (int w : g.adj(v)) {
            if (hasCycle()) {
                return;
            } else if (!marked[w]) {
                edgeTo[w] = v;
                dfs(g, w);
            } else if (onStack[w]) { // adopt onStack Record depth first searches each node if the node records marked. It means that there are directed rings in the graph
                cycle = new Stack<>();
                for (int x = v; x != w; x = edgeTo[x]) {
                    cycle.push(x);
                }
                cycle.push(w);
                cycle.push(v);
            }
        }
        onStack[v] = false;
    }

    public boolean hasCycle() {
        return cycle != null;
    }

    public Iterable<Integer> cycle() {
        return cycle;
    }

    public static void main(String[] args) {
        Digraph digraph = new Digraph(3);
        digraph.addEdge(0, 1);
        digraph.addEdge(1, 2);
        digraph.addEdge(2, 0);

        DirectedCycle cycle = new DirectedCycle(digraph);
        boolean b = cycle.hasCycle();
        System.out.println(b);
    }
}

3, The application of matrix in graph theory*

After being familiar with the algorithm of ring determination of undirected graph and directed graph, we should go deep into the essence of the algorithm. I will try to use matrix calculation to show you the charm of mathematical model. It doesn't matter if you're not familiar with matrix calculation, because this article won't give you any algorithmic implementation. You may wonder why you use the introduction matrix. We know that most algorithms are easily limited by CPU bottleneck when dealing with large data structures. If GPU can be used in parallel computing, the computing power can be greatly improved. The best operation GPU is matrix.

Returning to the graph given above, we use the row vector of the matrix to represent a connection and the column vector to represent a node. The figure on the left is as follows:

A1 =
    -1     1     0     0
     0    -1     1     0
     0     0    -1     1
     0     1     0    -1

The first row [- 11000] of the matrix indicates that edge 1(E1) connects node 0(N0) and node 1(N1), and the second row [0 - 11000] indicates that E2 connects N1 and N2, and so on. So how can we know if there are rings in the graph? Using Matlab as a tool, we use 4 x 4 unit matrix and A1 to form an augmented matrix, and use Gauss Jordan elimination for this matrix.

>> B1 = rref([A, eye(4)])

B1 =
     1     0     0    -1    -1     0     0     1
     0     1     0    -1     0     0     0     1
     0     0     1    -1     0     0    -1     0
     0     0     0     0     0     1     1     1

Looking at the first four columns of matrix B1, we can see that N0, N1, N2 and N3 are interconnected. And E4 does not affect the connectivity of the whole graph. Then observe the last four columns of matrix B1, which is changed from the unit matrix, and record the elimination process of A1. The fourth line [0111] indicates that E2, E3 and E4 of the original matrix A1 are added to 0. You can think that the connection 2, 3 and 4 of the graph form a ring. Note that since A1 represents an undirected ring, you can choose the direction of any two vertices in the join.

The same rule still holds in digraphs, just to show the direction of the connection, we need to specify the edge of N0 to N1 as [- 1 100]. Therefore, matrix A1 can still be used as the mathematical model of Figure 3. The last row [0111] of the last four columns can still be observed in the obtained B1, but its geometric meaning is different: remember our formula for ring determination in digraphs above: V4 + V2 + V3 = 0, yes, [0111] the real geometric meaning is the previous formula, but in the undirected graph, we simplify the judgment. Similarly, if we exchange 2 and 3 nodes of a digraph, we can still get the same result by matrix operation:

A2 =
    -1     1     0     0
     0    -1     0     1
     0     0     1    -1
     0     1    -1     0

>> B2 = rref([A2, eye(4)])

B2 =
     1     0     0    -1    -1     0     1     1
     0     1     0    -1     0     0     1     1
     0     0     1    -1     0     0     1     0
     0     0     0     0     0     1     1     1

4, Summary

At this point, my instructions on ring determination and matrix use are basically completed. If you don't know much about linear algebra, you may have trouble understanding the last paragraph. It doesn't matter, as I explained earlier, you don't need any knowledge about matrices, and I don't provide matching algorithms. The point of my last paragraph is to show you that graphs and matrix operations are closely related. Many seemingly profound algorithmic logic is actually the result of mathematical operation. If you can master the depth first algorithm of digraphs and undirected graphs after reading this article, and feel inspired in the theory of algorithm, this is enough.

Tags: Java network MATLAB

Posted on Mon, 18 May 2020 03:06:33 -0400 by lovasco