二分图简介
定义:二分图的顶点集可分割为两个互不相交的子集,图中每条边依附的两个顶点都分属于这两个子集,且两个子集内的顶点不相邻。
首先,二分图作为一种特殊的图模型,会被很多高级图算法(比如最大流算法)用到,不过这些高级算法我们不是特别有必要去掌握,有兴趣的读者可以自行搜索。
从简单实用的角度来看,二分图结构在某些场景可以更高效地存储数据。
每个电影节点的相邻节点就是参演该电影的所有演员,每个演员的相邻节点就是该演员参演过的所有电影,非常方便直观。类比这个例子,其实生活中不少实体的关系都能自然地形成二分图结构,所以在某些场景下图结构也可以作为存储键值对的数据结构(符号表)。
判定二分图
首先我们介绍双色问题:给定一幅图,请你用两种颜色将图中的所有顶点着色,且使得任意一条边的两个端点的颜色都不相同。
判定二分图的算法很简单,就是用代码解决「双色问题」。说白了就是遍历一遍图,一边遍历一遍染色,看看能不能用两种颜色给所有节点染色,且相邻节点的颜色都不相同。可以采用DFS或者BFS。
例题1
看一道例题:判断二分图
代码如下:
class Solution {
public:
vector<int> color;
vector<int> vis;
bool flag = true;
bool isBipartite(vector<vector<int>>& graph) {
int n = graph.size();
color.resize(n), vis.resize(n);
fill(color.begin(), color.begin() + n, 0);
fill(vis.begin(), vis.begin() + n, 0);
// 因为图不一定是联通的,可能存在多个子图
// 所以要把每个节点都作为起点进行一次遍历
// 如果发现任何一个子图不是二分图,整幅图都不算二分图
for(int v = 0; v < n; v++)
{
if((!vis[v]))
{
vis[v] = 1;
DFS(graph, v);
}
}
return flag;
}
void DFS(vector<vector<int>>& graph, int v)
{
// 如果已经确定不是二分图了,就不用浪费时间再递归遍历了
if(!flag) return;
for(auto& p: graph[v])
{
// 相邻节点 p 没有被访问过
// 那么应该给节点 p 涂上和节点 v 不同的颜色
if(!vis[p])
{
color[p] = (color[v] == 0 ? 1 : 0);
vis[p] = 1;
DFS(graph, p);
}
else
{
// 相邻节点 p 已经被访问过
// 根据 v 和 p 的颜色判断是否是二分图
if(color[v] == color[p])
{
flag = false;
return;
}
}
}
}
};
例题2
可能的二分法
显然,把人看作图中的节点,把讨厌的关系看作是边,两个互相讨厌的人不能在相邻节点,则意味着图中相邻节点不能在同一组。所以该题目等价为双色问题。
代码如下:
class Solution {
public:
bool color[2001] = {false};
bool vis[2001] = {false};
bool flag = true;
bool possibleBipartition(int n, vector<vector<int>>& dislikes)
{
//编号从1开始
//建图
vector<vector<int>> graph(n + 1, vector<int>());
for(auto& p : dislikes)
{
int v = p[0];
int w = p[1];
//无向图相当于双向图
graph[v].push_back(w);
graph[w].push_back(v);
}
for(int v = 1; v <= n; v++)
{
if(!flag) return false;
if(!vis[v])
{
vis[v] = true;
DFS(graph, v);
}
}
return flag;
}
void DFS(vector<vector<int>> graph, int v)
{
if(!flag) return;
for(auto& p : graph[v])
{
if(!vis[p])
{
color[p] = !color[v];
vis[p] = true;
DFS(graph, p);
}
else
{
if(color[p] == color[v])
{
flag = false;
return;
}
}
}
}
};