Depth first search of graph algorithm series (2)

Time:2021-9-10

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 paths 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 articlesHow 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 count 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

Depth first search of graph algorithm series (2)

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());
}

Depth first search of graph algorithm series (2)

In theory, the connectivity checking based on depth first search is faster than the previously implemented union find algorithm, because the version of checking connectivity depth first search can be completed in a constant time, but the union find algorithm can’t;

However, union find also has its own advantages: it does not 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 are 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:

Depth first search of graph algorithm series (2)

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 actors. Film is a vertex and actors are a vertex. There is no edge between film and film, and there is no edge between actors and actors. 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

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

last,Writing is not easy. Please don’t whore me for nothing, I hope friends canLike comments and concernsThird company, because these are all the power sources I share