Depth first search of graph algorithm series

Posted by sailu_mvn on Sat, 19 Feb 2022 19:35:42 +0100

Hematemesis sorting programmer must read list: https://github.com/silently9527/ProgrammerBooks

WeChat official account: beta Java

In the first part, we learned depth first search and knew how to find the path in the graph through depth first search; In this article, we continue to learn other application scenarios of depth first search algorithm

Connected component

Find all connected components from a graph, which is also an application scenario of depth first search. What is a connected component? This definition has been mentioned in previous articles How to detect whether two people in a social network are friends (Union find algorithm)

In this paper, we use the union find algorithm to check the connectivity. In this paper, we will use the depth first search method to find all connected components in the graph

API definition of connected components

public class DepthFirstCC {
    public DepthFirstCC(Graph graph); 
    
    public boolean connected(int v, int w); //Check whether the two vertices are connected

    public int count(); //Count the total number of connected components

    public int id(int v); //Identification of connected component of vertex v
}

API implementation of connected components

As before, we need to mark a vertex without scanning it, so we still need to define a marked [] array

In order to calculate the total number of connected components in the graph, you need to define a variable count

In order to judge whether two vertices are connected, we need to record the corresponding identification values of the connected vertices as the same value. When calling the connected method, we directly take out the identification values of the two vertices for comparison. If they are the same, they are connected, otherwise they are not connected;

We use the value of count for this identification value. Each vertex needs to store an identification value, so we also need an ids [] array.

public class DepthFirstCC {
    private boolean marked[];
    private int count;
    private int[] ids;

    public DepthFirstCC(Graph graph) {
        this.marked = new boolean[graph.V()];
        this.ids = new int[graph.V()];

        for (int v = 0; v < graph.V(); v++) {
            if (!this.marked[v]) {
                dfs(graph, v);
                count++;
            }
        }
    }

    private void dfs(Graph graph, int v) {
        this.marked[v] = true;
        this.ids[v] = count;
        for (int w : graph.adj(v)) {
            if (!this.marked[w]) {
                dfs(graph, w);
            }
        }
    }

    public boolean connected(int v, int w) {
        return id(v) == id(w);
    }

    public int count() {
        return count;
    }

    public int id(int v) {
        return ids[v];
    }

}

unit testing

To construct such a graph, the total number of connected components should be 3

@Test
public void test() {
    Graph graph = new Graph(10);
    graph.addEdge(0, 1);
    graph.addEdge(0, 2);
    graph.addEdge(0, 5);
    graph.addEdge(1, 3);
    graph.addEdge(2, 4);
    graph.addEdge(4, 3);
    graph.addEdge(5, 3);

    graph.addEdge(6, 7);

    graph.addEdge(8, 9);

    DepthFirstCC cc = new DepthFirstCC(graph);

    System.out.println(cc.connected(0,5));
    System.out.println(cc.connected(1,2));

    System.out.println(cc.count());
}

Theoretically, the connectivity check based on deep first search is faster than the previously implemented union find algorithm, because the version of checking connectivity deep first search can be completed in a constant time, but the union find algorithm can't;

However, union find also has its own advantages: there is no need to construct and represent a graph completely. More importantly, union find algorithm adds nodes dynamically.

Check for rings in the undirected graph

In order to reduce the complexity of implementation, we assume that there are no self rings and parallel edges in the graph;

If there is a ring starting from vertex v, which means that the adjacent vertex of a vertex in the connected component starting from vertex v is V, then vertex v must be encountered again in the process of search

Implementation ideas:

  1. Mark each vertex that has been searched
  2. When a marked vertex is encountered, it indicates that there is a ring in the graph;
  3. Because the graph is undirected, if v-w is connected, there will be w in the adjacency table of vertex v and V in the adjacency table of W, but they do not form a ring, so we need to eliminate this situation.
public class Cycle {
    private boolean marked[];
    private boolean hashCycle;

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

    private void dfs(Graph graph, int v, int pV) {
        this.marked[v] = true;
        for (int w : graph.adj(v)) {
            if (!this.marked[w]) {
                this.dfs(graph, w, v);
            } else if (w != pV) {
                this.hashCycle = true;
                return;
            }
        }
    }

    public boolean hasCycle() {
        return hashCycle;
    }
}

The parameter V of method dfs represents the vertex to be searched, and pV represents the vertex to reach v. therefore, if a vertex in the adjacency table of V has been marked and the vertex is not equal to the vertex to reach V, it indicates that there is a ring in the graph

Check whether the undirected graph is a bipartite graph

What is a bipartite graph? The vertices connected by each edge in the graph belong to different parts; As shown below:

The red node represents a set, the white node is another set, and the two vertices connected by each edge belong to different sets;

As a practical example, it is easy to understand the relationship between film and actor. Film is a vertex and actor is a vertex. There is no edge between film and film directly, and there is no edge between actor and actor directly. This is a bipartite graph.

public class TwoColorGraph {
    private boolean twoColor = true;
    private boolean[] marked;
    private boolean[] color;

    public TwoColorGraph(Graph graph) {
        this.marked = new boolean[graph.V()];
        this.color = new boolean[graph.V()];

        for (int v = 0; v < graph.V(); v++) {
            if (!this.marked[v]) {
                dfs(graph, v);
            }
        }
    }

    private void dfs(Graph graph, int v) {
        this.marked[v] = true;
        for (int w : graph.adj(v)) {
            if (!this.marked[w]) {
                this.color[w] = !this.color[v];
                dfs(graph, w);
            } else if (this.color[w] == this.color[v]) {
                this.twoColor = false;
                return;
            }
        }
    }

    public boolean isTwoColor() {
        return twoColor;
    }
}

All the source codes in this article have been put into the github warehouse:
https://github.com/silently9527/JavaCore

Last (focus, don't get lost)

There may be more or less deficiencies and mistakes in the article. If you have suggestions or opinions, you are welcome to comment and exchange.

Finally, writing is not easy. Please don't whore me for nothing. I hope friends can praise, comment and pay attention to Sanlian, because these are all the power sources I share 🙏

Topics: Java Algorithm