--- comments: true difficulty: 中等 edit_url: https://github.com/doocs/leetcode/edit/main/solution/0700-0799/0785.Is%20Graph%20Bipartite/README.md tags: - 深度优先搜索 - 广度优先搜索 - 并查集 - 图 --- # [785. 判断二分图](https://leetcode.cn/problems/is-graph-bipartite) [English Version](/solution/0700-0799/0785.Is%20Graph%20Bipartite/README_EN.md) ## 题目描述 存在一个 无向图 ,图中有 n 个节点。其中每个节点都有一个介于 0n - 1 之间的唯一编号。给你一个二维数组 graph ,其中 graph[u] 是一个节点数组,由节点 u 的邻接节点组成。形式上,对于 graph[u] 中的每个 v ,都存在一条位于节点 u 和节点 v 之间的无向边。该无向图同时具有以下属性:

二分图 定义:如果能将一个图的节点集合分割成两个独立的子集 AB ,并使图中的每一条边的两个节点一个来自 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} 。

 

提示:

## 解法 ### 方法一:染色法判定二分图 遍历所有节点进行染色,比如初始为白色,DFS 对节点相邻的点染上另外一种颜色。如果要染色某节点时,要染的目标颜色和该节点的已经染过的颜色不同,则说明不能构成二分图。 #### Python3 ```python class Solution: def isBipartite(self, graph: List[List[int]]) -> bool: def dfs(u, c): color[u] = c for v in graph[u]: if not color[v]: if not dfs(v, 3 - c): return False elif color[v] == c: return False return True n = len(graph) color = [0] * n for i in range(n): if not color[i] and not dfs(i, 1): return False return True ``` #### Java ```java class Solution { private int[] color; private int[][] g; public boolean isBipartite(int[][] graph) { int n = graph.length; color = new int[n]; g = graph; for (int i = 0; i < n; ++i) { if (color[i] == 0 && !dfs(i, 1)) { return false; } } return true; } private boolean dfs(int u, int c) { color[u] = c; for (int v : g[u]) { if (color[v] == 0) { if (!dfs(v, 3 - c)) { return false; } } else if (color[v] == c) { return false; } } return true; } } ``` #### C++ ```cpp class Solution { public: bool isBipartite(vector>& graph) { int n = graph.size(); vector color(n); for (int i = 0; i < n; ++i) if (!color[i] && !dfs(i, 1, color, graph)) return false; return true; } bool dfs(int u, int c, vector& color, vector>& g) { color[u] = c; for (int& v : g[u]) { if (!color[v]) { if (!dfs(v, 3 - c, color, g)) return false; } else if (color[v] == c) return false; } return true; } }; ``` #### Go ```go func isBipartite(graph [][]int) bool { n := len(graph) color := make([]int, n) var dfs func(u, c int) bool dfs = func(u, c int) bool { color[u] = c for _, v := range graph[u] { if color[v] == 0 { if !dfs(v, 3-c) { return false } } else if color[v] == c { return false } } return true } for i := range graph { if color[i] == 0 && !dfs(i, 1) { return false } } return true } ``` #### TypeScript ```ts function isBipartite(graph: number[][]): boolean { const n = graph.length; let valid = true; // 0 未遍历, 1 红色标记, 2 绿色标记 let colors = new Array(n).fill(0); function dfs(idx: number, color: number, graph: number[][]) { colors[idx] = color; const nextColor = 3 - color; for (let j of graph[idx]) { if (!colors[j]) { dfs(j, nextColor, graph); if (!valid) return; } else if (colors[j] != nextColor) { valid = false; return; } } } for (let i = 0; i < n && valid; i++) { if (!colors[i]) { dfs(i, 1, graph); } } return valid; } ``` #### Rust ```rust impl Solution { #[allow(dead_code)] pub fn is_bipartite(graph: Vec>) -> bool { let mut graph = graph; let n = graph.len(); let mut color_vec: Vec = vec![0; n]; for i in 0..n { if color_vec[i] == 0 && !Self::traverse(i, 1, &mut color_vec, &mut graph) { return false; } } true } #[allow(dead_code)] fn traverse( v: usize, color: usize, color_vec: &mut Vec, graph: &mut Vec>, ) -> bool { color_vec[v] = color; for n in graph[v].clone() { if color_vec[n as usize] == 0 { // This node hasn't been colored if !Self::traverse(n as usize, 3 - color, color_vec, graph) { return false; } } else if color_vec[n as usize] == color { // The color is the same return false; } } true } } ``` ### 方法二:并查集 对于本题,如果是二分图,那么图中每个顶点的所有邻接点都应该属于同一集合,且不与顶点处于同一集合,因此我们可以使用并查集。遍历图中每个顶点,如果发现存在当前顶点与对应的邻接点处于同一个集合,说明不是二分图。否则将当前节点的邻接点相互进行合并。以下是并查集模板。 模板 1——朴素并查集: ```python # 初始化,p存储每个点的父节点 p = list(range(n)) # 返回x的祖宗节点 def find(x): if p[x] != x: # 路径压缩 p[x] = find(p[x]) return p[x] # 合并a和b所在的两个集合 p[find(a)] = find(b) ``` 模板 2——维护 size 的并查集: ```python # 初始化,p存储每个点的父节点,size只有当节点是祖宗节点时才有意义,表示祖宗节点所在集合中,点的数量 p = list(range(n)) size = [1] * n # 返回x的祖宗节点 def find(x): if p[x] != x: # 路径压缩 p[x] = find(p[x]) return p[x] # 合并a和b所在的两个集合 if find(a) != find(b): size[find(b)] += size[find(a)] p[find(a)] = find(b) ``` 模板 3——维护到祖宗节点距离的并查集: ```python # 初始化,p存储每个点的父节点,d[x]存储x到p[x]的距离 p = list(range(n)) d = [0] * n # 返回x的祖宗节点 def find(x): if p[x] != x: t = find(p[x]) d[x] += d[p[x]] p[x] = t return p[x] # 合并a和b所在的两个集合 p[find(a)] = find(b) d[find(a)] = distance ``` #### Python3 ```python class Solution: def isBipartite(self, graph: List[List[int]]) -> bool: def find(x): if p[x] != x: p[x] = find(p[x]) return p[x] p = list(range(len(graph))) for u, g in enumerate(graph): for v in g: if find(u) == find(v): return False p[find(v)] = find(g[0]) return True ``` #### Java ```java class Solution { private int[] p; public boolean isBipartite(int[][] graph) { int n = graph.length; p = new int[n]; for (int i = 0; i < n; ++i) { p[i] = i; } for (int u = 0; u < n; ++u) { int[] g = graph[u]; for (int v : g) { if (find(u) == find(v)) { return false; } p[find(v)] = find(g[0]); } } return true; } private int find(int x) { if (p[x] != x) { p[x] = find(p[x]); } return p[x]; } } ``` #### C++ ```cpp class Solution { public: vector p; bool isBipartite(vector>& graph) { int n = graph.size(); p.resize(n); for (int i = 0; i < n; ++i) p[i] = i; for (int u = 0; u < n; ++u) { auto& g = graph[u]; for (int v : g) { if (find(u) == find(v)) return 0; p[find(v)] = find(g[0]); } } return 1; } int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } }; ``` #### Go ```go func isBipartite(graph [][]int) bool { n := len(graph) p := make([]int, n) for i := range p { p[i] = i } var find func(x int) int find = func(x int) int { if p[x] != x { p[x] = find(p[x]) } return p[x] } for u, g := range graph { for _, v := range g { if find(u) == find(v) { return false } p[find(v)] = find(g[0]) } } return true } ``` #### TypeScript ```ts function isBipartite(graph: number[][]): boolean { const n = graph.length; let p = new Array(n); for (let i = 0; i < n; ++i) { p[i] = i; } function find(x) { if (p[x] != x) { p[x] = find(p[x]); } return p[x]; } for (let u = 0; u < n; ++u) { for (let v of graph[u]) { if (find(u) == find(v)) { return false; } p[find(v)] = find(graph[u][0]); } } return true; } ``` #### Rust ```rust impl Solution { #[allow(dead_code)] pub fn is_bipartite(graph: Vec>) -> bool { let n = graph.len(); let mut disjoint_set: Vec = vec![0; n]; // Initialize the disjoint set for i in 0..n { disjoint_set[i] = i; } // Traverse the graph for i in 0..n { if graph[i].is_empty() { continue; } let first = graph[i][0] as usize; for v in &graph[i] { let v = *v as usize; let i_p = Self::find(i, &mut disjoint_set); let v_p = Self::find(v, &mut disjoint_set); if i_p == v_p { return false; } // Otherwise, union the node Self::union(first, v, &mut disjoint_set); } } true } #[allow(dead_code)] fn find(x: usize, d_set: &mut Vec) -> usize { if d_set[x] != x { d_set[x] = Self::find(d_set[x], d_set); } d_set[x] } #[allow(dead_code)] fn union(x: usize, y: usize, d_set: &mut Vec) { let p_x = Self::find(x, d_set); let p_y = Self::find(y, d_set); d_set[p_x] = p_y; } } ```