【图】力扣785:判断二分图(待更并查集)

存在一个 无向图 ,图中有 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 之间可能不存在一条连通彼此的路径。

如果图是二分图,返回 true ;否则,返回 false 。

示例1:

image
输入:graph = [[1,3],[0,2],[1,3],[0,2]]。输入是邻接链表表示的图(如位置 0 的邻接链表为 [1,3],表示 0 与 1、 0 与 3 相连)
输出:true
解释:可以将节点分成两组: {0, 2} 和

示例2:

image
输入:graph = [[1,2,3],[0,2],[0,1,3],[0,2]]
输出:false
解释:不能将节点分割成两个独立的子集,以使每条边都连通一个子集中的一个节点与另一个子集中的一个节点。

基础知识

  1. 二分图

若无向图 G = (V, E) 的顶点集 V 可以分割为两个互不相交的子集,且图中每条边的两个顶点分别属于不同的子集,则称图 G 为一个二分图。

  1. 二分图算法

二分图算法也称为染色法,是一种广度优先搜索。如果可以用两种颜色对图中的节点进行着色,并且保证相邻的节点颜色不同,那么图为二分。

算法流程:

  • 所有结点均为无色(0)

  • 任选一个结点开始,将其染成红色(1),并从该结点开始对整个无向图进行遍历

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

    • 如果 v 未被染色,就将其染成与 u 不同的颜色如绿色(-1),并对 v 直接相连的结点进行遍历

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

  • 遍历顺利结束则说明给定的无向图是二分图,返回 true。

注意:题目给定的无向图不一定保证连通,因此需要进行多次遍历,直到每一个结点都被染色,或确定答案为 false 为止。

深度优先搜索 DFS

class Solution:
    def isBipartite(self, graph: List[List[int]]) -> bool:
        n = len(graph) # 点的个数
        if n == 0:
            return True

        colors = [0] * n # 染色数组,所有元素初始化为 0。colors[i] 标记结点 i 的状态,0 为未着色,1 为着红色,-1 为着绿色
        red, green = 1, -1 # 不可以不规定 red,因为调用 dfs 时需要用到

        def dfs(node, color):
            colors[node] = color # 给当前结点着色
            color_neighber = -color # color_neighber = green if color == red else red,因为设置的颜色代数为相反数,所以可以巧妙地用负号而不是判断语句
            for neighber in graph[node]: # 遍历结点所在的连通图
                if  colors[neighber] == 0: # 没有染色就给染相反色
                    colors[neighber] = color_neighber
                    if not dfs(neighber, color_neighber):
                        return False
                elif colors[neighber] == color_neighber: # 染色合规则跳过,不需重复着色
                    continue
                else: # 染色矛盾,返回false
                    return False
            return True

        for i in range(n): # 遍历每一个结点,也就是给其连通图染色
            if colors[i] == 0: # 如果该结点未被染色,启动搜索对该连通图进行染色
                if not dfs(i, red): # 给第一个结点染上红色
                    return False
        return True

也可以用一个 flag 变量来记录布尔值:

class Solution:
    def isBipartite(self, graph: List[List[int]]) -> bool:
        n = len(graph)
        red, green = 1, 2
        color = [0] * n
        valid = True

        def dfs(node: int, color: int):
            nonlocal valid # 声明之后的变量既不是全局变量,也不是局部变量,只在调用的子函数中发挥作用
            color[node] = color
            color_neighber = - color
            for neighbor in graph[node]:
                if color[neighbor] == 0:
                    dfs(neighbor, color_neighber)
                    if not valid:
                        return
                elif color[neighbor] != color_neighber: # else 就是合规的情况,此时不需要再写,因为 valid 默认是true
                    valid = False
                    return

        for i in range(n):
            if color[i] == 0:
                dfs(i, red)
                if not valid:
                    break
        return valid

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/is-graph-bipartite/solution/pan-duan-er-fen-tu-by-leetcode-solution/

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

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

广度优先搜索 BFS

新建队列queue

class Solution:
    def isBipartite(self, graph: List[List[int]]) -> bool:
        n = len(graph)
        if n == 0:
            return True

        colors = [0] * n
        red, green = 1, -1

        def bfs(node, color):
            queue = [[node, color]]
            while queue:
                node, color = queue.pop(0) # 给当前结点着色
                color_neighber = -color # 给邻接结点着色
                for neighber in graph[node]:
                    if  colors[neighber] == 0: # 没有染色
                       colors[neighber] = color_neighber
                       queue.append([neighber, color_neighber])
                    elif colors[neighber] == color_neighber: # 染色合规
                        continue
                    else: # 染色矛盾
                        return False
            return True

        for i in range(n): # 遍历每一个结点,也就是给其连通图染色
            if colors[i] == 0: # 如果该结点未被染色,启动搜索对该连通图进行染色
                if not bfs(i, red): # 给第一个结点染上红色
                    return False
        return True

不额外增加函数版本:

from collections import deque 
class Solution:
    def isBipartite(self, graph: List[List[int]]) -> bool:
        n = len(graph)
        colors = [0] * n # 不需规定 red 或 green,考虑的时候想着给两个颜色赋相反值即可,

        for i in range(n):
            if colors[i] == 0:
                colors[i] = 1 # 对当前结点染红色
                queue = deque()
                queue.append(i)
                while queue: # 从当前结点 node 开始搜索当前连通图。只有当当前结点没被染色时才搜索,已经染色了就循环到graph的下一个结点
                    node = queue.popleft()
                    color = colors[node] # 注意是取当前结点的颜色,而不是虚变量 i
                    for neighber in graph[node]: # 遍历当前结点的所有邻接点
                        if colors[neighber] == 0: # 邻接点未染色就给染色
                            colors[neighber] = - color
                            queue.append(neighber)
                        elif colors[neighber] == color: # 邻接点颜色与结点 node 颜色相同,说明矛盾,不是二分图,直接返回false
                            return False
                        else: # 邻接点颜色与结点 node 颜色不同,说明已经入队过了,无需重复操作
                            continue
        return True

作者:an-zhi-ruo-su
链接:https://leetcode.cn/problems/is-graph-bipartite/solution/bfs-zhi-xing-yong-shi-216-ms-zai-suo-you-python3-t/

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

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

并查集

如果图是二分图的话,那么图中每个顶点的所有邻接点都应该属于同一集合,且不与顶点处于同一集合。因此可以使用并查集来解决这个问题:

遍历图中每个顶点,将当前顶点的所有邻接点进行合并,并判断这些邻接点中是否存在某一邻接点已经和当前顶点处于同一个集合中,若是,则说明不是二分图。

class unionfind:
    def __init__(self,n):
        self.father={}
        for i in range(n):
            self.father[i]=i

    def find(self, u):
        root_u=u
        while self.father[root_u]!=root_u:
            root_u=self.father[root_u]
        return root_u

    def union(self, u, v):
        root_u=self.find(u)
        root_v=self.find(v)
        if root_u!=root_v:
            self.father[root_u]=root_v

graph = [[1,3],[0,2],[1,3],[0,2]]

def isBipartite(graph):
    n=len(graph)
    uf=unionfind(n)
    for u in range(n):
        len_nei=len(graph[u])
        for i in range(len_nei):
            v1=graph[u][i]
            # 如果遍历到另一个节点 u 时 发现 u 和它的某一个邻居 v 是联通的 则返回 False
            if uf.find(u)==uf.find(v1):
                return False
            # 遍历每一个点,将该点的邻居结点连接起来
            for j in range(i+1, len_nei):
                v2=graph[u][j]
                uf.union(v1, v2)
    return True

作者:yuer-flyfly
链接:https://leetcode.cn/problems/is-graph-bipartite/solution/pan-duan-er-fen-tu-tu-pan-duan-wu-xiang-dqalo/
posted @ 2022-07-26 11:55  Vonos  阅读(138)  评论(0)    收藏  举报