785.力扣LeetCode_ 判断二分图
目录
思路一:并查集
方法1:邻居并查集
方法2:影子并查集
两种方法总结
思路二:染色法
方法3:深度优先dfs
方法4:广度优先bfs
4种方法总结


-
思路一:并查集
-
方法1:邻居并查集
核心思想:如果一个点和它的邻居属于同一个集合,那么这张图就不是二分图。在遍历每个顶点时,我们检查它的每一个邻居。若发现当前顶点 i 和邻居 i_neighbor 属于同一集合,就说明出现了冲突,因为相邻节点不可能在同一阵营。否则,我们把i的所有邻居都并入同一个集合,表示这些邻居属于与 i 对立的阵营。这种方法利用并查集来维护“阵营内部的连通性”,让同一阵营的节点保持在同一个集合中。
#define MaxSize 100
int parent[MaxSize],rank[MaxSize];void Initialize(int n){for(int i=0;i<n;i++){parent[i]=i;rank[i]=1;}
}int find(int x){if(parent[x]!=x){parent[x]=find(parent[x]);}return parent[x];
}void Union(int x,int y){int x_root=find(x);int y_root=find(y);if(x_root!=y_root){if(rank[x_root]>rank[y_root]){parent[y_root]=x_root;}else if(rank[x_root]<rank[y_root]){parent[x_root]=y_root;}else{parent[y_root]=x_root;rank[x_root]++;}}
}bool isBipartite(int** graph, int graphSize, int* graphColSize) {int n=graphSize;Initialize(n);for(int i=0;i<n;i++){for(int j=0;j<graphColSize[i];j++){int i_neighbor=graph[i][j];if(find(i)==find(i_neighbor)){return false;}else{Union(graph[i][0],i_neighbor);}}}return true;
}
-
方法2:影子并查集
核心思路:把原来的图复制一份,形成一个“影子层”,并让每个原节点在影子层上都有一个对应点。例如,原图中的节点编号为 0 到 n-1,影子层上的节点编号为 n 到 2n-1,其中 i 对应的影子节点是 i + n。可以把这看作在原图的上方叠加了一层完全对应的影子图。当原图中存在一条边 (i, j) 时,表示 i 和 j 必须位于不同的层。因此,我们把 i 与 j 的影子节点连接在一起,同时把 j 与 i 的影子节点连接在一起。若在过程中发现某个节点和它的邻居被划分到同一组,就说明无法分出上下两层,图不是二分图。
#define MaxSize 200
int parent[MaxSize],rank[MaxSize];void Initialize(int n){for(int i=0;i<n;i++){parent[i]=i;rank[i]=1;}
}int find(int x){if(parent[x]!=x){parent[x]=find(parent[x]);}return parent[x];
}void Union(int x,int y){int x_root=find(x);int y_root=find(y);if(x_root!=y_root){if(rank[x_root]>rank[y_root]){parent[y_root]=x_root;}else if(rank[x_root]<rank[y_root]){parent[x_root]=y_root;}else{parent[y_root]=x_root;rank[x_root]++;}}
}bool isBipartite(int** graph, int graphSize, int* graphColSize) {int n=graphSize;Initialize(n*2);for(int i=0;i<n;i++){for(int j=0;j<graphColSize[i];j++){int i_neighbor=graph[i][j];if(find(i)==find(i_neighbor)){return false;}else{Union(i,i_neighbor+n);Union(i+n,i_neighbor);}}}return true;
}
-
两种方法总结
“一种在原图中隐式完成划分,另一种在叠加的影子图中显式地表达出来。”
邻居并查集直接在原图中进行操作,通过邻居关系逐步确定分层,思路更直接。影子并查集则通过在原图上叠加影子层,把“上下分层”的关系显式地表达出来。前者是隐式地维护层的区分;后者是明确地建立两层之间的映射。邻居并查集结构更简单,但影子并查集在逻辑上更清晰,也更容易推广到需要显式映射关系的问题中。
-
思路二:染色法
-
方法3:深度优先dfs
从一个未染色的节点开始,给它染上一种颜色。然后立刻递归地去访问它的一个邻居节点,给邻居染上相反的颜色,再从邻居出发继续深入。当递归返回时,继续检查当前节点的其他邻居。如果在任何时候发现邻居的颜色与当前节点相同,就说明图不是二分图。这种方法利用了函数调用栈来实现"回溯"的功能,逻辑上非常直接。
bool dfs(int cur,int** graph,int* graphColSize,int n,int visited[],int flag){if(visited[cur]!=0) return true;visited[cur]=flag;for(int i=0;i<graphColSize[cur];i++){int cur_nrighbor=graph[cur][i];if(visited[cur_nrighbor]==flag){return false;}else if(visited[cur_nrighbor]==0){if(!dfs(cur_nrighbor,graph,graphColSize,n,visited,-flag)){return false;}}}return true;
}bool isBipartite(int** graph, int graphSize, int* graphColSize) {int n=graphSize;int visited[n];bool res=true;memset(visited,0,n*sizeof(int));for(int i=0;i<n;i++){if(visited[i]==0){res=dfs(i,graph,graphColSize,n,visited,1);if(!res){return false;}}}return true;
}
-
方法4:广度优先bfs
使用一个队列来管理所有待处理的节点。从一个未染色的节点开始,给它染色并加入队列。然后从队列中取出一个节点,给它所有未染色的邻居染上相反的颜色,并将这些新染色的邻居加入队列,等待下一轮处理。如果在处理过程中发现邻居的颜色与当前节点相同,就说明图不是二分图。这种方法利用队列保证了处理的顺序是"先入队的节点先处理",从而实现了"逐层扩散"。
bool bfs(int cur,int** graph,int* graphColSize,int n,int visited[],int flag){int queue[n],front=0,rear=0;queue[rear++]=cur;visited[cur]=flag;while(front!=rear){int x=queue[front++];for(int i=0;i<graphColSize[x];i++){int x_neighbor=graph[x][i];if(visited[x_neighbor]==visited[x]){return false;}else if(visited[x_neighbor]==0){queue[rear++]=x_neighbor;visited[x_neighbor]=-visited[x];}}}return true;
}bool isBipartite(int** graph, int graphSize, int* graphColSize) {int n=graphSize;int visited[n];bool res=true;memset(visited,0,n*sizeof(int));for(int i=0;i<n;i++){if(visited[i]==0){res=bfs(i,graph,graphColSize,n,visited,1);if(!res){return false;}}}return true;
}
-
4种方法总结
这四种方法可以分为两大类:并查集法和染色法。它们解决的问题本质相同,都是要判断一张图能否被分成两个互不相连的阵营,只是思维角度不同。
并查集方法属于结构划分型。它的核心思想是“集合关系”。通过并与查操作,维护节点之间的从属关系,用集合结构隐含地表达“在同一阵营”或“在不同阵营”。在邻居并查集中,节点之间的对立关系是通过邻居集合的合并间接建立的;而影子并查集则更进一步,把这种关系显式地表达为“原节点与影子节点的对应”。前者逻辑简洁、实现直接;后者虽然空间更大,但概念清晰、层次分明,尤其适合需要同时维护“正反阵营映射”的问题。
染色法属于状态标记型。它通过给节点染上不同颜色来区分阵营,直接体现了图的二分性。深度优先(DFS)染色法的特点是“纵向推进”,从一个节点出发,递归地深入邻居并反转颜色,一旦检测到颜色冲突就返回。它结构紧凑,利用系统栈实现回溯,但不适合极深的图。广度优先(BFS)染色法则是“横向推进”,从一个节点出发逐层扩散,用队列来管理染色顺序。它能更好地处理大规模稀疏图,也避免了递归带来的栈深问题。
从算法思想上看,并查集法强调“关系的维护”,是一种静态的集合逻辑;而染色法强调“状态的传播”,是一种动态的过程逻辑。前者更接近数学中的“等价类划分”,后者更像是图遍历中的“逻辑一致性检验”。两种思维方式从不同角度诠释了“相邻节点必须分属不同阵营”的约束:并查集通过合并和影射实现,染色法通过递推和对比实现。
从适用性来看,并查集方法在图较小或需要处理复杂映射关系时表现更好;染色法则更灵活,易于与图遍历框架结合,也更直观地反映二分图的层次结构。无论哪一种,核心目标都是通过某种形式的“相反标记”机制来判定冲突。
总体而言,这四种方法构成了一个从“结构化划分”到“动态传播”的完整思维体系:并查集负责建立层的逻辑基础,染色法负责在遍历过程中验证一致性。前者强调关系,后者强调过程;前者更抽象,后者更直观。二者互为补充,共同刻画了二分图判断问题的核心本质。
