After reading this article, you will not only learn the algorithm routines, but also take the following topics on LeetCode:

  1. Judgment bipartite graph (medium)
  2. Possible dichotomy (medium)

-----------

I have written several articles related to graph theory before:

Graph traversal algorithm

celebrity question

search algorithm to calculate connected components

Ring detection and topological sorting

Dijkstra shortest path algorithm

Today, I will continue to talk about a classic graph theory algorithm: bipartite graph judgment.

Introduction to bipartite graphs

Before talking about the judgment algorithm of the bipartite graph, let's take a look at Baidu Baike's definition of "bipartite graph":

The vertex set of a bipartite graph can be divided into two disjoint subsets. The two vertices attached to each edge of the graph belong to these two subsets, and the vertices in the two subsets are not adjacent.

In fact, the definitions of many terms in graph theory are rather confusing and difficult to understand. Let's not look at this rigid definition, let's play a game:

gives you a "graph". Please use two colors to color all the vertices in the graph so that the colors of the two endpoints of any edge are different. Can you do ?

This is the "two-color problem" of the graph. In fact, this problem is equivalent to the judgment problem of the bipartite graph. If you can successfully color the graph, then the graph is a bipartite graph, and vice versa:

Before explaining the bipartite graph decision algorithm in detail, let's talk about the purpose of solving the two-color problem by the computer bigwigs.

First of all, as a special graph model, the bipartite graph will be used by many advanced graph algorithms (such as the maximum flow algorithm), but it is not necessary for us to master these advanced algorithms. Interested readers can search by themselves.

From a simple and practical point of view, the bipartite graph structure can store data more efficiently in certain scenarios.

For example, the previous article introduced the example in the article "Algorithm 4" , how to store the relationship between movie actors and movies?

If a hash table is used for storage, two hash tables are needed to store the mapping of "each actor to movie list" and the mapping of "each movie to actor list" respectively.

But if you use the "picture" structure to store, link the movie and the actors, it will naturally become a bipartite picture:

The adjacent nodes of each movie node are all the actors who participated in the movie, and the adjacent nodes of each actor are all the movies that the actor has participated in, which is very convenient and intuitive.

Analogy to this example, in fact, the relationship of many entities in life can naturally form a bipartite graph structure, so in some scenarios, the graph structure can also be used as a data structure (symbol table) for storing key-value pairs.

Okay, let’s move on to the topic and talk about how to determine whether a picture is a bipartite picture.

Bipartite Graph Judgment Idea

The algorithm to determine the bipartite graph is very simple, which is to use code to solve the "two-color problem".

means to traverse the graph once, while traversing the coloring once, to see if all nodes can be colored with two colors, and the colors of adjacent nodes are not the same .

Since it is talking about traversal graphs, there is no shortest path and so on. Of course, DFS algorithm and BFS are both available. DFS algorithm is relatively more commonly used, so let's take a look at how to use DFS algorithm to determine two-color graph.

First of all, based on learning data structure and algorithm framework thinking write the traversal framework of the graph:

/* 二叉树遍历框架 */
void traverse(TreeNode root) {
    if (root == null) return;
    traverse(root.left);
    traverse(root.right);
}

/* 多叉树遍历框架 */
void traverse(Node root) {
    if (root == null) return;
    for (Node child : root.children)
        traverse(child);
}

/* 图遍历框架 */
boolean[] visited;
void traverse(Graph graph, int v) {
    // 防止走回头路进入死循环
    if (visited[v]) return;
    // 前序遍历位置,标记节点 v 已访问
    visited[v] = true;
    for (TreeNode neighbor : graph.neighbors(v))
        traverse(graph, neighbor);
}

Because there may be a ring in the figure, the visited array is used to prevent going back.

Here you can see that I am used to putting return statements at the beginning of the function, because generally return statements are base cases. Putting them together can make the algorithm structure clearer.

In fact, if you want, you can also put the if judgment in other places. For example, the graph traversal framework can be slightly changed:

/* 图遍历框架 */
boolean[] visited;
void traverse(Graph graph, int v) {
    // 前序遍历位置,标记节点 v 已访问
    visited[v] = true;
    for (int neighbor : graph.neighbors(v)) {
        if (!visited[neighbor]) {
            // 只遍历没标记过的相邻节点
            traverse(graph, neighbor);
        }
    }
}

This way of writing puts visited before the recursive call. The only difference from the previous way of writing is that you need to ensure that when calling traverse(v) , visited[v] == false .

Why do you have to say this way of writing in particular? Because we judge the bipartite graph algorithm will use this way of writing.

review how to judge the bipartite graph, in fact, let the traverse function traverse the nodes while coloring the nodes, trying to make the color of each pair of adjacent nodes different .

Therefore, the code logic to determine the bipartite graph can be written like this:

/* 图遍历框架 */
void traverse(Graph graph, boolean[] visited, int v) {
    visited[v] = true;
    // 遍历节点 v 的所有相邻节点 neighbor
    for (int neighbor : graph.neighbors(v)) {
        if (!visited[neighbor]) {
            // 相邻节点 neighbor 没有被访问过
            // 那么应该给节点 neighbor 涂上和节点 v 不同的颜色
            traverse(graph, visited, neighbor);
        } else {
            // 相邻节点 neighbor 已经被访问过
            // 那么应该比较节点 neighbor 和节点 v 的颜色
            // 若相同,则此图不是二分图
        }
    }
}

If you can understand the above code, you can write the specific code for the bipartite graph judgment. Next, let's look at two specific algorithm problems to practice it.

Topic practice

The 785th question "Judging the bipartite graph" is the original question. The question gives you a adjacency table that represents an undirected graph. Please judge whether this graph is a bipartite graph.

The function signature is as follows:

boolean isBipartite(int[][] graph);

For example, the example given in the title, the input adjacency table graph = [[1,2,3],[0,2],[0,1,3],[0,2]] , is such a picture:

Obviously it is impossible to color the nodes so that the colors of every two adjacent nodes are different, so the algorithm returns false.

But if you enter graph = [[1,3],[0,2],[1,3],[0,2]] , it will be a picture like this:

If the node {0, 2} painted with one color and the node {1, 3} painted with another color, the "two-color problem" can be solved, so this is a bipartite graph, and the algorithm returns true.

Combined with the previous code framework, we can additionally use an color to record the color of each node to write the solution code:

// 记录图是否符合二分图性质
private boolean ok = true;
// 记录图中节点的颜色,false 和 true 代表两种不同颜色
private boolean[] color;
// 记录图中节点是否被访问过
private boolean[] visited;

// 主函数,输入邻接表,判断是否是二分图
public boolean isBipartite(int[][] graph) {
    int n = graph.length;
    color =  new boolean[n];
    visited =  new boolean[n];
    // 因为图不一定是联通的,可能存在多个子图
    // 所以要把每个节点都作为起点进行一次遍历
    // 如果发现任何一个子图不是二分图,整幅图都不算二分图
    for (int v = 0; v < n; v++) {
        if (!visited[v]) {
            traverse(graph, v);
        }
    }
    return ok;
}

// DFS 遍历框架
private void traverse(int[][] graph, int v) {
    // 如果已经确定不是二分图了,就不用浪费时间再递归遍历了
    if (!ok) return;

    visited[v] = true;
    for (int w : graph[v]) {
        if (!visited[w]) {
            // 相邻节点 w 没有被访问过
            // 那么应该给节点 w 涂上和节点 v 不同的颜色
            color[w] = !color[v];
            // 继续遍历 w
            traverse(graph, w);
        } else {
            // 相邻节点 w 已经被访问过
            // 根据 v 和 w 的颜色判断是否是二分图
            if (color[w] == color[v]) {
                // 若相同,则此图不是二分图
                ok = false;
            }
        }
    }
}

This is the code to solve the "two-color problem". If the entire image can be dyed successfully, it means that it is a bipartite graph, otherwise it is not a bipartite graph.

Next, look at the logic of the BFS algorithm:

// 记录图是否符合二分图性质
private boolean ok = true;
// 记录图中节点的颜色,false 和 true 代表两种不同颜色
private boolean[] color;
// 记录图中节点是否被访问过
private boolean[] visited;

public boolean isBipartite(int[][] graph) {
    int n = graph.length;
    color =  new boolean[n];
    visited =  new boolean[n];
    
    for (int v = 0; v < n; v++) {
        if (!visited[v]) {
            // 改为使用 BFS 函数
            bfs(graph, v);
        }
    }
    
    return ok;
}

// 从 start 节点开始进行 BFS 遍历
private void bfs(int[][] graph, int start) {
    Queue<Integer> q = new LinkedList<>();
    visited[start] = true;
    q.offer(start);
    
    while (!q.isEmpty() && ok) {
        int v = q.poll();
        // 从节点 v 向所有相邻节点扩散
        for (int w : graph[v]) {
            if (!visited[w]) {
                // 相邻节点 w 没有被访问过
                // 那么应该给节点 w 涂上和节点 v 不同的颜色
                color[w] = !color[v];
                // 标记 w 节点,并放入队列
                visited[w] = true;
                q.offer(w);
            } else {
                // 相邻节点 w 已经被访问过
                // 根据 v 和 w 的颜色判断是否是二分图
                if (color[w] == color[v]) {
                    // 若相同,则此图不是二分图
                    ok = false;
                }
            }
        }
    }
}

The core logic is traverse function (DFS algorithm) just implemented, and it is also judged based on the colors of the v and w For the discussion of the BFS algorithm framework, see the previous BFS algorithm framework and Dijkstra algorithm template , which will not be expanded here.

Finally, let’s take a look at Likou Question 886 "Possible Dichotomies":

The function signature is as follows:

boolean possibleBipartition(int n, int[][] dislikes);

In fact, this question examines the judgment of the :

If you regard everyone as a node in the graph, and the mutual annoying relationship as an edge in the graph, then the dislikes array can form a graph;

And because the title says that people who hate each other cannot be placed in the same group, which is equivalent to placing all adjacent nodes in the graph into two different groups;

Then back to the "two-color problem". If you can color all nodes with two colors, and the adjacent nodes have different colors, then you can divide these nodes into two groups according to the colors.

So the solution comes out. We dislikes into a picture, and then execute the bipartite graph judgment algorithm:

private boolean ok = true;
private boolean[] color;
private boolean[] visited;

public boolean possibleBipartition(int n, int[][] dislikes) {
    // 图节点编号从 1 开始
    color = new boolean[n + 1];
    visited = new boolean[n + 1];
    // 转化成邻接表表示图结构
    List<Integer>[] graph = buildGraph(n, dislikes);
    
    for (int v = 1; v <= n; v++) {
        if (!visited[v]) {
            traverse(graph, v);
        }
    }
    
    return ok;
}

// 建图函数
private List<Integer>[] buildGraph(int n, int[][] dislikes) {
    // 图节点编号为 1...n
    List<Integer>[] graph = new LinkedList[n + 1];
    for (int i = 1; i <= n; i++) {
        graph[i] = new LinkedList<>();
    }
    for (int[] edge : dislikes) {
        int v = edge[1];
        int w = edge[0];
        // 「无向图」相当于「双向图」
        // v -> w
        graph[v].add(w);
        // w -> v
        graph[w].add(v);
    }
    return graph;
}

// 和之前的 traverse 函数完全相同
private void traverse(List<Integer>[] graph, int v) {
    if (!ok) return;
    visited[v] = true;
    for (int w : graph[v]) {
        if (!visited[w]) {
            color[w] = !color[v];
            traverse(graph, w);
        } else {
            if (color[w] == color[v]) {
                ok = false;
            }
        }
    }
}

At this point, this problem has also been solved using the DFS algorithm. If you want to use the BFS algorithm, it is exactly the same as the solution written before, you can try to implement it yourself.

The decision algorithm of the bipartite graph is here. More advanced algorithms for the bipartite graph, please stay tuned.

_____________

View more high-quality algorithm articles Click on my avatar , and take you through the buckle hand by hand, dedicated to making the algorithm clear! My algorithm tutorial has won 90k stars, please like it!

labuladong
63 声望38 粉丝