二分图的概念
二分图的顶点集可分割为两个互不相交的子集,图中每条边依附的两个顶点都分属于这两个子集,且两个子集内的顶点不相邻。
只看定义或许让我们觉得晦涩难懂,简单来说,二分图符合以下特征:图中的每一条边相连的两个点不属于同一组集合(如果我们将图中的每个点都涂上一种颜色,图中共两种颜色:那么每一条边的两个点的颜色必定是不同的)
首先,二分图作为一种特殊的图模型,会被很多高级图算法(比如最大流算法)用到,不过这些高级算法我们不是特别有必要去掌握,有兴趣的读者可以自行搜索。
从简单实用的角度来看,二分图结构在某些场景可以更高效地存储数据。
比如说我们需要一种数据结构来储存电影和演员之间的关系:某一部电影肯定是由多位演员出演的,且某一位演员可能会出演多部电影。你使用什么数据结构来存储这种关系呢?
既然是存储映射关系,最简单的不就是使用哈希表嘛,我们可以使用一个
HashMap<String, List<String>>
来存储电影到演员列表的映射,如果给一部电影的名字,就能快速得到出演该电影的演员。但是如果给出一个演员的名字,我们想快速得到该演员演出的所有电影,怎么办呢?这就需要「反向索引」,对之前的哈希表进行一些操作,新建另一个哈希表,把演员作为键,把电影列表作为值。
显然,如果用哈希表存储,需要两个哈希表分别存储「每个演员到电影列表」的映射和「每部电影到演员列表」的映射。但如果用「图」结构存储,将电影和参演的演员连接,很自然地就成为了一幅二分图:
二分图解题模板
二分图题目的解题模板如下:
/* 图遍历框架 */
void traverse(Graph graph, boolean[] visited, int v) {
visited[v] = true;
// 遍历节点 v 的所有相邻节点 neighbor
for (int neighbor : graph.neighbors(v)) {
if (!visited[neighbor]) {
// 相邻节点 neighbor 没有被访问过
// 那么应该给节点 neighbor 涂上和节点 v 不同的颜色
traverse(graph, visited, neighbor);
} else {
// 相邻节点 neighbor 已经被访问过
// 那么应该比较节点 neighbor 和节点 v 的颜色
// 若相同,则此图不是二分图
}
}
}
题目
题目描述
力扣https://leetcode.cn/problems/vEAB3K/
存在一个 无向图 ,图中有 n 个节点。其中每个节点都有一个介于 0 到 n - 1 之间的唯一编号。
给定一个二维数组 graph ,表示图,其中 graph[u] 是一个节点数组,由节点 u 的邻接节点组成。形式上,对于 graph[u] 中的每个 v ,都存在一条位于节点 u 和节点 v 之间的无向边。该无向图同时具有以下属性:
不存在自环(graph[u] 不包含 u)。
不存在平行边(graph[u] 不包含重复值)。
如果 v 在 graph[u] 内,那么 u 也应该在 graph[v] 内(该图是无向图)
这个图可能不是连通图,也就是说两个节点 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
解题思路
其实此题就是在遍历图结构的基础上进行一点改动:图遍历时只需要遍历,但是这个题目在遍历的同时要求将对应的结点进行分组(将节点涂上与之对应的颜色),这样就能把保证每个遍历过的结点都涂上了对应的颜色,不断进行递归,直到我们遍历到了之间遍历过的结点,发现当前结点的颜色和其相连的结点的颜色相同,出现了冲突,此时我们则判定这个图并非二分图(未遍历过则遍历并标注颜色,并以此进行下一步的递归,如果结点在此前遍历过,此时需要对比该结点与其相连的结点颜色是否冲突,不冲突进行回溯即可,如果冲突,利用我们之前设置的标志符key,将key置为false,直接返回)
实例代码
class Solution {
//定义结果
boolean ok=true;
//定义数组判断保存颜色
boolean[]color;
//定义数组判断是否添加过
boolean[]visited;
public boolean isBipartite(int[][] graph) {
int size=graph.length;
//初始化
color=new boolean[size];
visited=new boolean[size];
//避免存在多个图
for(int i=0;i<size;++i){
if(!visited[i])
reverse(graph,i);
}
return ok;
}
//递归函数
public void reverse(int[][]graph,int index){
//出口条件
if(!ok){
return;
}
//将结果设置为参观
visited[index]=true;
//处理邻接节点
for(int element:graph[index]){
if(!visited[element]){
color[element]=!color[index];
//递归
reverse(graph,element);
}else{
if(color[element]==color[index]){
ok=false;
return;
}
}
}
}
}