LCR 106-判断二分图

Raphael Liu Lv10

存在一个 无向图 ,图中有 n 个节点。其中每个节点都有一个介于 0n - 1 之间的唯一编号。

给定一个二维数组 graph ,表示图,其中 graph[u] 是一个节点数组,由节点 u 的邻接节点组成。形式上,对于 graph[u]
中的每个 v ,都存在一条位于节点 u 和节点 v 之间的无向边。该无向图同时具有以下属性:

  • 不存在自环(graph[u] 不包含 u)。
  • 不存在平行边(graph[u] 不包含重复值)。
  • 如果 vgraph[u] 内,那么 u 也应该在 graph[v] 内(该图是无向图)
  • 这个图可能不是连通图,也就是说两个节点 uv 之间可能不存在一条连通彼此的路径。

二分图 定义:如果能将一个图的节点集合分割成两个独立的子集 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} 。

提示:

  • 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

注意:本题与主站 785 题相同: https://leetcode-cn.com/problems/is-graph-bipartite/

前言

对于图中的任意两个节点 u 和 v,如果它们之间有一条边直接相连,那么 u 和 v 必须属于不同的集合。

如果给定的无向图连通,那么我们就可以任选一个节点开始,给它染成红色。随后我们对整个图进行遍历,将该节点直接相连的所有节点染成绿色,表示这些节点不能与起始节点属于同一个集合。我们再将这些绿色节点直接相连的所有节点染成红色,以此类推,直到无向图中的每个节点均被染色。

如果我们能够成功染色,那么红色和绿色的节点各属于一个集合,这个无向图就是一个二分图;如果我们未能成功染色,即在染色的过程中,某一时刻访问到了一个已经染色的节点,并且它的颜色与我们将要给它染上的颜色不相同,也就说明这个无向图不是一个二分图。

算法的流程如下:

  • 我们任选一个节点开始,将其染成红色,并从该节点开始对整个无向图进行遍历;

  • 在遍历的过程中,如果我们通过节点 u 遍历到了节点 v(即 u 和 v 在图中有一条边直接相连),那么会有两种情况:

    • 如果 v 未被染色,那么我们将其染成与 u 不同的颜色,并对 v 直接相连的节点进行遍历;

    • 如果 v 被染色,并且颜色与 u 相同,那么说明给定的无向图不是二分图。我们可以直接退出遍历并返回 false 作为答案。

  • 当遍历结束时,说明给定的无向图是二分图,返回 true 作为答案。

我们可以使用「深度优先搜索」或「广度优先搜索」对无向图进行遍历,下文分别给出了这两种搜索对应的代码。

注意:题目中给定的无向图不一定保证连通,因此我们需要进行多次遍历,直到每一个节点都被染色,或确定答案为 false 为止。每次遍历开始时,我们任选一个未被染色的节点,将所有与该节点直接或间接相连的节点进行染色。

方法一:深度优先搜索

[sol1-C++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
private:
static constexpr int UNCOLORED = 0;
static constexpr int RED = 1;
static constexpr int GREEN = 2;
vector<int> color;
bool valid;

public:
void dfs(int node, int c, const vector<vector<int>>& graph) {
color[node] = c;
int cNei = (c == RED ? GREEN : RED);
for (int neighbor: graph[node]) {
if (color[neighbor] == UNCOLORED) {
dfs(neighbor, cNei, graph);
if (!valid) {
return;
}
}
else if (color[neighbor] != cNei) {
valid = false;
return;
}
}
}

bool isBipartite(vector<vector<int>>& graph) {
int n = graph.size();
valid = true;
color.assign(n, UNCOLORED);
for (int i = 0; i < n && valid; ++i) {
if (color[i] == UNCOLORED) {
dfs(i, RED, graph);
}
}
return valid;
}
};
[sol1-Java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Solution {
private static final int UNCOLORED = 0;
private static final int RED = 1;
private static final int GREEN = 2;
private int[] color;
private boolean valid;

public boolean isBipartite(int[][] graph) {
int n = graph.length;
valid = true;
color = new int[n];
Arrays.fill(color, UNCOLORED);
for (int i = 0; i < n && valid; ++i) {
if (color[i] == UNCOLORED) {
dfs(i, RED, graph);
}
}
return valid;
}

public void dfs(int node, int c, int[][] graph) {
color[node] = c;
int cNei = c == RED ? GREEN : RED;
for (int neighbor : graph[node]) {
if (color[neighbor] == UNCOLORED) {
dfs(neighbor, cNei, graph);
if (!valid) {
return;
}
} else if (color[neighbor] != cNei) {
valid = false;
return;
}
}
}
}
[sol1-Python3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution:
def isBipartite(self, graph: List[List[int]]) -> bool:
n = len(graph)
UNCOLORED, RED, GREEN = 0, 1, 2
color = [UNCOLORED] * n
valid = True

def dfs(node: int, c: int):
nonlocal valid
color[node] = c
cNei = (GREEN if c == RED else RED)
for neighbor in graph[node]:
if color[neighbor] == UNCOLORED:
dfs(neighbor, cNei)
if not valid:
return
elif color[neighbor] != cNei:
valid = False
return

for i in range(n):
if color[i] == UNCOLORED:
dfs(i, RED)
if not valid:
break

return valid

[sol1-C]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
bool dfs(int node, int c, int* color, int** graph, int* graphColSize) {
color[node] = c;
int cNei = (c == 1 ? 2 : 1);
for (int i = 0; i < graphColSize[node]; ++i) {
int neighbor = graph[node][i];
if (color[neighbor] == 0) {
if (!dfs(neighbor, cNei, color, graph, graphColSize)) {
return false;
}
} else if (color[neighbor] != cNei) {
return false;
}
}
return true;
}

bool isBipartite(int** graph, int graphSize, int* graphColSize) {
int* color = (int*)malloc(sizeof(int) * graphSize);
memset(color, 0, sizeof(int) * graphSize);
for (int i = 0; i < graphSize; ++i) {
if (color[i] == 0) {
if (!dfs(i, 1, color, graph, graphColSize)) {
free(color);
return false;
}
}
}
free(color);
return true;
}
[sol1-Golang]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
var (
UNCOLORED, RED, GREEN = 0, 1, 2
color []int
valid bool
)

func isBipartite(graph [][]int) bool {
n := len(graph)
valid = true
color = make([]int, n)
for i := 0; i < n && valid; i++ {
if color[i] == UNCOLORED {
dfs(i, RED, graph)
}
}
return valid
}

func dfs(node, c int, graph [][]int) {
color[node] = c
cNei := RED
if c == RED {
cNei = GREEN
}
for _, neighbor := range graph[node] {
if color[neighbor] == UNCOLORED {
dfs(neighbor, cNei, graph)
if !valid {
return
}
} else if color[neighbor] != cNei {
valid = false
return
}
}
}

复杂度分析

  • 时间复杂度:O(n+m),其中 n 和 m 分别是无向图中的点数和边数。

  • 空间复杂度:O(n),存储节点颜色的数组需要 O(n) 的空间,并且在深度优先搜索的过程中,栈的深度最大为 n,需要 O(n) 的空间。

方法二:广度优先搜索

[sol2-C++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {
private:
static constexpr int UNCOLORED = 0;
static constexpr int RED = 1;
static constexpr int GREEN = 2;
vector<int> color;

public:
bool isBipartite(vector<vector<int>>& graph) {
int n = graph.size();
vector<int> color(n, UNCOLORED);
for (int i = 0; i < n; ++i) {
if (color[i] == UNCOLORED) {
queue<int> q;
q.push(i);
color[i] = RED;
while (!q.empty()) {
int node = q.front();
int cNei = (color[node] == RED ? GREEN : RED);
q.pop();
for (int neighbor: graph[node]) {
if (color[neighbor] == UNCOLORED) {
q.push(neighbor);
color[neighbor] = cNei;
}
else if (color[neighbor] != cNei) {
return false;
}
}
}
}
}
return true;
}
};
[sol2-Java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {
private static final int UNCOLORED = 0;
private static final int RED = 1;
private static final int GREEN = 2;
private int[] color;

public boolean isBipartite(int[][] graph) {
int n = graph.length;
color = new int[n];
Arrays.fill(color, UNCOLORED);
for (int i = 0; i < n; ++i) {
if (color[i] == UNCOLORED) {
Queue<Integer> queue = new LinkedList<Integer>();
queue.offer(i);
color[i] = RED;
while (!queue.isEmpty()) {
int node = queue.poll();
int cNei = color[node] == RED ? GREEN : RED;
for (int neighbor : graph[node]) {
if (color[neighbor] == UNCOLORED) {
queue.offer(neighbor);
color[neighbor] = cNei;
} else if (color[neighbor] != cNei) {
return false;
}
}
}
}
}
return true;
}
}
[sol2-Python3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution:
def isBipartite(self, graph: List[List[int]]) -> bool:
n = len(graph)
UNCOLORED, RED, GREEN = 0, 1, 2
color = [UNCOLORED] * n

for i in range(n):
if color[i] == UNCOLORED:
q = collections.deque([i])
color[i] = RED
while q:
node = q.popleft()
cNei = (GREEN if color[node] == RED else RED)
for neighbor in graph[node]:
if color[neighbor] == UNCOLORED:
q.append(neighbor)
color[neighbor] = cNei
elif color[neighbor] != cNei:
return False

return True
[sol2-C]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
bool isBipartite(int** graph, int graphSize, int* graphColSize) {
int* color = (int*)malloc(sizeof(int) * graphSize);
memset(color, 0, sizeof(int) * graphSize);

int* q = (int*)malloc(sizeof(int) * graphSize);
for (int i = 0; i < graphSize; ++i) {
if (color[i] == 0) {
int l = 0, r = 0;
q[0] = i;
color[i] = 1;
while (l <= r) {
int node = q[l++];
int cNei = (color[node] == 1 ? 2 : 1);
for (int j = 0; j < graphColSize[node]; ++j) {
int neighbor = graph[node][j];
if (color[neighbor] == 0) {
q[++r] = neighbor;
color[neighbor] = cNei;
} else if (color[neighbor] != cNei) {
free(color);
free(q);
return false;
}
}
}
}
}
free(color);
free(q);
return true;
}
[sol2-Golang]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var (
UNCOLORED, RED, GREEN = 0, 1, 2
)

func isBipartite(graph [][]int) bool {
n := len(graph)
color := make([]int, n)
for i := 0; i < n; i++ {
if color[i] == UNCOLORED {
queue := []int{}
queue = append(queue, i)
color[i] = RED
for i := 0; i < len(queue); i++ {
node := queue[i]
cNei := RED
if color[node] == RED {
cNei = GREEN
}
for _, neighbor := range graph[node] {
if color[neighbor] == UNCOLORED {
queue = append(queue, neighbor)
color[neighbor] = cNei
} else if color[neighbor] != cNei {
return false
}
}
}
}
}
return true
}

复杂度分析

  • 时间复杂度:O(n+m),其中 n 和 m 分别是无向图中的点数和边数。

  • 空间复杂度:O(n),存储节点颜色的数组需要 O(n) 的空间,并且在广度优先搜索的过程中,队列中最多有 n-1 个节点,需要 O(n) 的空间。

 Comments
On this page
LCR 106-判断二分图