目录
- 题目描述
- 解题过程
- 完整代码
- 改进
- 改进代码
题目描述
解题过程
这道题与书上习题3.7类似。二部图是这样的图G=(V,E),其顶点集合可以被划分为两个子集(V = V1 ∪ V2 且 V1 ∩ V2 = Ф),并且子集内部的顶点之间没有边相连。
要判断一个无向图是否是二部图,我们可以考虑这个图是否可以用两种颜色为它着色,有边相连的两个顶点需要着不同颜色。
首先,用一个数组subsets来记录每个顶点的颜色,-1表示未着色,而0,1分别表示两种不同的颜色。
vector<int> subsets;
for(int i = 0; i < graph.size(); i++) subsets.push_back(-1);
由于给定的图可能由多个强连通部件构成,因此需要对每个顶点进行检查。如果当前顶点已经着色,则可以跳过,因为这表示该顶点所在的强连通部件已经被着色了。如果有一个强连通部件不能用两种颜色着色,则返回false,如果所有强连通部件都可以用两种颜色着色,则返回true。
for(int i = 0; i < graph.size(); i++) {
if(subsets[i] != -1) continue;
if(!explore(subsets, graph, i)) return false;
}
explore使用深度优先遍历,对一个强连通部件进行着色。如果该部件不能用两种颜色着色,则返回false。
bool explore(vector<int>& subsets, vector<vector<int>>& graph, int i) {
if(subsets[i] == -1) subsets[i] = 0;
for(int j = 0; j < graph[i].size(); j++) {
if(subsets[graph[i][j]] != -1) {
if(subsets[graph[i][j]] == subsets[i]) return false;
continue;
}
subsets[graph[i][j]] = !subsets[i];
if(graph[graph[i][j]].size() > 0) {
if(!explore(subsets, graph, graph[i][j])) return false;
}
}
return true;
}
这是个线性时间算法,复杂度为O(V+E)。
完整代码
class Solution {
public:
bool isBipartite(vector<vector<int>>& graph) {
vector<int> subsets;
for(int i = 0; i < graph.size(); i++) subsets.push_back(-1);
for(int i = 0; i < graph.size(); i++) {
if(subsets[i] != -1) continue;
if(!explore(subsets, graph, i)) return false;
}
return true;
}
bool explore(vector<int>& subsets, vector<vector<int>>& graph, int i) {
if(subsets[i] == -1) subsets[i] = 0;
for(int j = 0; j < graph[i].size(); j++) {
if(subsets[graph[i][j]] != -1) {
if(subsets[graph[i][j]] == subsets[i]) return false;
continue;
}
subsets[graph[i][j]] = !subsets[i];
if(graph[graph[i][j]].size() > 0) {
if(!explore(subsets, graph, graph[i][j])) return false;
}
}
return true;
}
};
改进
O(V+E)已经是最快的算法了,但是我们还可以对之前的代码进行简化,使代码更简洁易懂。
这次改进,直接将subsets声明为一个大小为graph.size(),即顶点数的数组,0表示为着色,而1,-1分别表示两种不同的颜色。由于如果当前顶点已经着色,explore在一开始就会返回,因此可以不用在for循环中判断该顶点是否着色。
bool isBipartite(vector<vector<int>>& graph) {
vector<int> subsets(graph.size());
for(int i = 0; i < graph.size(); i++) {
if(subsets[i] == 0 && !explore(subsets, graph, i, 1)) return false;
}
return true;
}
explore增加了参数color,如果当前顶点已着色,则说明有环,只需检验当前顶点是否符合规定,否则会陷入死循环。如果没有着色,则给该顶点着色,并且分别检查与该顶点相连的其它顶点。
bool explore(vector<int>& subsets, vector<vector<int>>& graph, int i, int color) {
if(subsets[i] != 0) return subsets[i] == color;
subsets[i] = color;
for(auto node : graph[i]) {
if(!explore(subsets, graph, node, -1 * color)) return false;
}
return true;
}
第二种算法与第一种算法有略微的不同,主要是,第二种算法的思路是先规定当前顶点应该有的颜色,然后来检验当前顶点是否符合规定,而第一种则是在exlpore中进行着色,然后检验其后续顶点是否符合规定。当然,复杂度都是O(V + E)。
改进代码
class Solution {
public:
bool isBipartite(vector<vector<int>>& graph) {
vector<int> subsets(graph.size());
for(int i = 0; i < graph.size(); i++) {
if(!explore(subsets, graph, i, 1)) return false;
}
return true;
}
bool explore(vector<int>& subsets, vector<vector<int>>& graph, int i, int color) {
if(subsets[i] != 0) return subsets[i] == color;
subsets[i] = color;
for(auto node : graph[i]) {
if(!explore(subsets, graph, node, -1 * color)) return false;
}
return true;
}
};