Is Graph Bipartite?

1. 题目

Given an undirected graph, return true if and only if it is bipartite.

Recall that a graph is bipartite if we can split it’s set of nodes into two independent subsets A and B such that every edge in the graph has one node in A and another node in B.

The graph is given in the following form: graph[i] is a list of indexes j for which the edge between nodes i and j exists. Each node is an integer between 0 and graph.length - 1. There are no self edges or parallel edges: graph[i] does not contain i, and it doesn’t contain any element twice.

Example 1:
Input: [[1,3], [0,2], [1,3], [0,2]]
Output: true
Explanation:
The graph looks like this:
0----1
| |
| |
3----2
We can divide the vertices into two groups: {0, 2} and {1, 3}.
Example 2:
Input: [[1,2,3], [0,2], [0,1,3], [0,2]]
Output: false
Explanation:
The graph looks like this:
0----1
| \ |
| \ |
3----2
We cannot find a way to divide the set of nodes into two independent subsets.

Note:

graph will have length in range [1, 100].
graph[i] will contain integers in range [0, graph.length - 1].
graph[i] will not contain i or duplicate values.
The graph is undirected: if any element j is in graph[i], then i will be in graph[j].

2. 基础知识

这题是二分图的相关知识,二分图又称二部图,是图论的一种特殊模型。其定义为:设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。

3. 题目分析

题目给出一个图,图的形式是下标对应的是相应的顶点,而下标对应的vector是与这个下标对应顶点相连的顶点,要求我们判断该图是不是二分图。根据二分图的定义,图中相连的顶点之间要被分在两个不同的集合中,转化为我们所学的图的相关方面的知识,我们需要确定两种标记,相连的两个顶点之间标记必须是不一样的,否则不能满足二分图的定义。确定了基本的策略,那我们如何去实现这种策略呢?基于这个标记是针对每个顶点的,我猜测遍历是需要的,图的遍历有两种方式,我们需要做出选择。
再根据题目的要求,由于与一个顶点相连的全部顶点的标记都应该与这个顶点的标记不一样,而广度遍历就是先遍历这个点,然后是遍历这个点所有与之相连的点,所以两者不谋而合,所以是首选广度遍历。
但最后发现深度遍历也是可以的,不过对标记的处理要比较灵活。如果用普通的bool类型来判断,那么对于已经访问和未访问的点就会比较难以区别,所以我们的标记必须有三个值,然后想到了-1,0,1这三个数,初始的未访问的状态用0表示,当我们递归的时候,不断传递标记的相反数即可。

4. 算法步骤

  • 广度优先遍历
    • 从每个顶点开始,如果顶点没被染色,将顶点染色为-1并将当点push进队列中。
    • 如果队列不空,取队首,将与队首有边的顶点染成与队首颜色数互为相反数的颜色;如果发现颜色数相同,结束整个过程,说明不能构成二分图。
    • 成功第一个步骤确保所有的点被遍历到,因为有非连通图的情况。
    • 所有点遍历完,返回true。
  • 深度优先遍历
    • 对每个点进行深度优先遍历。
    • 在递归过程传递的参数是前一个顶点的相反数,确保有边相连的顶点染上不同颜色。
    • 在递归过程发现同一条边相连的两个顶点颜色一样,递归结束,返回false。

5. 源码

  • 5.1 广度遍历实现
class Solution {
public:
    bool isBipartite(vector<vector<int>>& graph) {
        vector<int> color(graph.size(), 0);
        queue<int> q;
        for(int i = 0; i < graph.size(); i++) {
        	if(color[i] == 0) {
        		color[i] = -1;
        		q.push(i);
        		while(!q.empty()) {
        			int temp = q.front();
        			q.pop();
        			for(int j = 0; j < graph[temp].size(); j++) {
        				if(color[graph[temp][j]] == 0) {
        					color[graph[temp][j]] = 0 - color[temp];
        					q.push(graph[temp][j]);
        				}
        				if(color[temp] == color[graph[temp][j]]) {
        					return false;
        				}
        			}
        		}
        	}
        }
        return true;
    }
};
  • 5.2 深度遍历实现
class Solution {
public:
	bool dfs(int source, vector<vector<int>>& graph, vector<int>& color, int current) {
		color[source] = current;
		for(int i = 0; i < graph[source].size(); i++) {
			if(color[graph[source][i]] == current) {
				return false;
			}
			if(color[graph[source][i]] == 0) {
				bool b = dfs(graph[source][i], graph, color, -current);
				if(!b) {
					return false;
				}
			}
		}
		return true;
	}

    bool isBipartite(vector<vector<int>>& graph) {
        vector<int> color(graph.size(), 0);
        int c = 1;
        for(int i = 0; i < graph.size(); i++) {
        	if(color[i] == 0) {
        		bool b = dfs(i, graph, color, c);
        		if(b == false) {
        			return false;
        		}
        	}
        }
        return true;
    }
};

5. 算法复杂度分析

由于这两种方法都是在广度优先和深度优先遍历的基础上做出的修改,所以时间复杂度也正是这两种遍历的时间复杂度,也就是线性的·时间复杂度O(V+E);

10-04 10:08