存在一个 无向图 ,图中有 n 个节点。其中每个节点都有一个介于 0 到 n - 1 之间的唯一编号。给你一个二维数组 graph ,其中 graph[u] 是一个节点数组,由节点 u 的邻接节点组成。形式上,对于 graph[u] 中的每个 v ,都存在一条位于节点 u 和节点 v 之间的无向边。该无向图同时具有以下属性:
1. 不存在自环(graph[u] 不包含 u)。
2. 不存在平行边(graph[u] 不包含重复值)。
3. 如果 v 在 graph[u] 内,那么 u 也应该在 graph[v] 内(该图是无向图)
4. 这个图可能不是连通图,也就是说两个节点 u 和 v 之间可能不存在一条连通彼此的路径。
二分图定义:如果能将一个图的节点集合分割成两个独立的子集 A 和 B ,并使图中的每一条边的两个节点一个来自 A 集合,一个来自 B 集合,就将这个图称为二分图 。
如果图是二分图,返回 true ;否则,返回 false 。
示例 1:
输入:graph = [[1,2,3],[0,2],[0,1,3],[0,2]]
输出:false
解释:不能将节点分割成两个独立的子集,以使每条边都连通一个子集中的一个节点与另一个子集中的一个节点。
示例 2:
输入:graph = [[1,3],[0,2],[1,3],[0,2]]
输出:true
解释:可以将节点分成两组: {0, 2} 和 {1, 3} 。
提示:
graph.length == n
1 <= n <= 100
0 <= graph[u].length < n
0 <= graph[u][i] <= n - 1
graph[u] 不会包含 u
graph[u] 的所有值 互不相同
如果 graph[u] 包含 v,那么 graph[v] 也会包含 u
思路
- 将所有节点分为两个组,group1和group2
- 仅当所有group1的节点和k之间没有边,group[k]=1;仅当所有group2的节点和k之间没有边,group[k]=2
- 等价于:任一个group1的节点和k之间存在边, group[k]≠1;任一个group2的节点和k之间存在边, group[k]≠2
- 等价于:group1的节点与k之间存在边,且在同一集合,则不可能存在二分图
- 注意:两个节点之间没有边时可以在同一集合,但是有边就一定不能在同一集合,所以要用
有边+在同一集合
判断不存在二分图
代码1–双指针
class Solution {
public:
bool dfs(vector<vector<int>>& graph, vector<int>& group, int k)
{
if (k >= graph.size()) {
return true;
}
for (int i = 0; i < k; ++i) {
if (find(graph[i].begin(), graph[i].end(), k) != graph[i].end()) {
if (group[k] == 0) {
group[k] = 3 - group[i];
} else if (group[k] == group[i]) {
return false;
}
}
}
return dfs(graph, group, k + 1);
}
bool isBipartite(vector<vector<int>>& graph) {
vector<int> group(graph.size(), 0); // 1和2各为一组
group[0] = 1; // 将第一个节点放入第一集合
return dfs(graph, group, 1);
}
};
观察上述代码,本质就是一个双指针
class Solution {
public:
bool isBipartite(vector<vector<int>>& graph) {
vector<int> group(graph.size(), 0); // 1和2各为一组
group[0] = 1; // 将第一个节点放入第一集合
for (int i = 1; i < graph.size(); ++i) {
for (int j = 0; j < i; ++j) {
if (find(graph[j].begin(), graph[j].end(), i) != graph[j].end()) {
if (group[i] == 0) {
group[i] = 3 - group[j];
} else if (group[i] == group[j]) {
return false;
}
}
}
}
return true;
}
};
代码2-搜索(DFS/BFS)
参考:BFS + DFS + 并查集,三种方法判断二分图
根据二分图的定义,我们可以使用图搜索算法从各个连通域的任一顶点开始遍历整个连通域,遍历的过程中用两种不同的颜色对顶点进行染色,相邻顶点染成相反的颜色。这个过程中倘若发现相邻的顶点被染成了相同的颜色,说明它不是二分图;反之,如果所有的连通域都染色成功,说明它是二分图。
其实跟划分集合的思路是类似的。
- DFS:
class Solution {
public:
bool dfs(vector<vector<int>>& graph, int u, int color, vector<int>& visit) {
// 如果当前顶点已经染色,判断其与本次要染的颜色是否相同
if (visit[u]) {
return visit[u] == color;
}
// 对当前顶点进行染色,其并将其临界点染成相反的颜色
visit[u] = color;
for (const auto& v : graph[u]) {
if (!dfs(graph, v, -color, visit)) {
return false;
}
}
return true;
}
bool isBipartite(vector<vector<int>>& graph) {
vector<int> visit(graph.size(), 0); // 0表示未访问,-1和1各作为一个颜色
// 注意有多个连通域,不能直接return dfs
// 只有所有连通域都能成功,才存在二分图
for (int i = 0; i < graph.size(); ++i) {
if (!visit[i] && !dfs(graph, i, -1, visit)) {
return false;
}
}
return true;
}
};
- BFS:
class Solution {
public:
bool isBipartite(vector<vector<int>>& graph) {
vector<int> visit(graph.size(), 0); // 0表示未访问,-1和1各作为一个颜色
queue<int> q;
// 注意存在多个连通域
for (int i = 0; i < graph.size(); ++i) {
if (visit[i] != 0) {
continue;
}
q.push(i); // 顶点入队
visit[i] = -1; // 给顶点染色
while (!q.empty()) {
// 取出一个节点
int u = q.front();
q.pop();
// 给其相邻节点染色
for (const auto& v : graph[u]) {
// 如果已经染过色了,且颜色与当前节点相同,则无法构成二分图
if (visit[v] == visit[u]) {
return false;
}
// 如果还没染色就给它染色,并加入队列中
if (visit[v] == 0) {
visit[v] = -visit[u];
q.push(v);
}
}
}
}
return true;
}
};
代码3-并查集
参考:BFS + DFS + 并查集,三种方法判断二分图
我们知道如果是二分图的话,那么图中每个顶点的所有邻接点都应该属于同一集合,且不与顶点处于同一集合。因此我们可以使用并查集来解决这个问题,我们遍历图中每个顶点,将当前顶点的所有邻接点进行合并,并判断这些邻接点中是否存在某一邻接点已经和当前顶点处于同一个集合中了,若是,则说明不是二分图。
class UnionFind {
public:
// 初始化并查集
UnionFind(int n) {
pre.resize(n);
for (int i = 0; i < n; ++i) {
pre[i] = i;
}
}
// 查找根节点
int Find(int x) {
return x == pre[x] ? x : (pre[x] = Find(pre[x]));
}
// 合并两个节点
void Union(int x, int y) {
pre[Find(x)] = Find(y);
}
// 判断两个顶点是否在同一个集合中
bool isConnected(int x, int y) {
return Find(x) == Find(y);
}
private:
vector<int> pre;
};
class Solution {
public:
bool isBipartite(vector<vector<int>>& graph) {
UnionFind uf(graph.size());
// 遍历每个顶点,将其所有邻接顶点进行合并
for (int i = 0; i < graph.size(); ++i) {
for (const int j : graph[i]) {
if (uf.isConnected(i, j)) {
return false;
}
// 将临界点合并
uf.Union(j, graph[i][0]);
}
}
return true;
}
};