LeetCode-785. 判断二分图(附带算法详解)

LeetCode-785. 判断二分图(附带算法详解)

-.- 这题不会做,记录下大佬的题解(难度:中等)

一、题目内容

给定一个无向图graph,当这个图为二分图时返回true。
如果我们能将一个图的节点集合分割成两个独立的子集A和B,并使图中的每一条边的两个节点一个来自A集合,一个来自B集合,我们就将这个图称为二分图。

graph将会以邻接表方式给出,graph[i]表示图中与节点i相连的所有节点。每个节点都是一个在0到graph.length-1之间的整数。这图中没有自环和平行边: graph[i] 中不存在i,并且graph[i]中没有重复的值。
示例 1:
输入: [[1,3], [0,2], [1,3], [0,2]]
输出: true
解释:
无向图如下:
0----1
| |
| |
3----2
我们可以将节点分成两组: {0, 2} 和 {1, 3}。

示例 2:
输入: [[1,2,3], [0,2], [0,1,3], [0,2]]
输出: false
解释:
无向图如下:
0----1
| \ |
| \ |
3----2
我们不能将节点分割成两个独立的子集。

二、题解过程

1、什么是二分图

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

2、判断二分图

1)深度优先搜索/广度优先搜索
我们使用图搜索算法从各个连通域的任一顶点开始遍历整个连通域,遍历的过程中用两种不同的颜色对顶点进行染色,相邻顶点染成相反的颜色。这个过程中倘若发现相邻的顶点被染成了相同的颜色,说明它不是二分图;反之,如果所有的连通域都染色成功,说明它是二分图。
2)并查集
我们知道如果是二分图的话,那么图中每个顶点的所有邻接点都应该属于同一集合,且不与顶点处于同一集合。因此我们可以使用并查集来解决这个问题,我们遍历图中每个顶点,将当前顶点的所有邻接点进行合并,并判断这些邻接点中是否存在某一邻接点已经和当前顶点处于同一集合了,若是,则说明不是二分图。

3、算法简介

1)广度优先遍历(BFS)
简单来说从初始点出发,一层一层从内而外的遍历方式,就叫做广度优先遍历,本质上与二叉树的层序遍历差不多。

实现广度优先遍历的关键在于[重放],即是把遍历过的顶点按照之前的遍历顺序重新回顾,就叫做重放。要实现重放也需要利用额外的存储空间,可以利用队列的先入先出特性来实现。

那么无论是哪种遍历方法,当我获取一个顶点的若干邻近顶点时,如果判断这些顶点哪个已经被访问过,哪个没有被访问过?
我们可以利用一个布尔类型的数组来存储所有顶点的遍历状态,顶点对应数组元素的初始值都是false,代表未遍历,遍历之后变为true。

参考链接:https://blog.csdn.net/weixin_30722589/article/details/97363179.

2)深度优先遍历(DFS)
简单的来说就是先深入探索,走到头再回退寻找其他出路的遍历方式,就叫做深度优先遍历。本质上与二叉树的前序、中序、后序遍历差不多。

实现深度优先遍历的关键在于[回溯],即是自后向前的追溯曾经访问过的路径。要想实现回溯,可以利用栈的先入后出特性,也可以利用递归的方式(因为递归本身就是基于调用栈来实现的)。

那么无论是哪种遍历方法,当我获取一个顶点的若干邻近顶点时,如果判断这些顶点哪个已经被访问过,哪个没有被访问过?
我们可以利用一个布尔类型的数组来存储所有顶点的遍历状态,顶点对应数组元素的初始值都是false,代表未遍历,遍历之后变为true

参考链接:https://blog.csdn.net/weixin_30722589/article/details/97363179.

4、解法

解法一(深度优先遍历):

// A code block
class Solution {
   public boolean isBipartite(int[][] graph) {
        // 定义 visited 数组,初始值为 0 表示未被访问,赋值为 1 或者 -1 表示两种不同的颜色。 
        int[] visited = new int[graph.length];
        // 因为图中可能含有多个连通域,所以我们需要判断是否存在顶点未被访问,若存在则从它开始再进行一轮 dfs 染色。
        for (int i = 0; i < graph.length; i++) {
            if (visited[i] == 0 && !dfs(graph, i, 1, visited)) {
                return false;
            }
        }
        return true;
    }

    private boolean dfs(int[][] graph, int v, int color, int[] visited) {
        // 如果要对某顶点染色时,发现它已经被染色了,则判断它的颜色是否与本次要染的颜色相同,如果矛盾,说明此无向图无法被正确染色,返回 false。
        if (visited[v] != 0) {
            return visited[v] == color;
        }
        
        // 对当前顶点进行染色,并将当前顶点的所有邻接点染成相反的颜色。
        visited[v] = color;
        for (int w: graph[v]) {
            if (!dfs(graph, w, -color, visited)) {
                return false;
            }
        }
        return true;
    }
}

解法二(广度优先遍历)

// B code block
class Solution {
    public boolean isBipartite(int[][] graph) {
		int[] grep = new int[graph.length];
        Queue queue = new LinkedList<>();
        //因为图中可能存在多个连通域,所以我们需要判断是否存在顶点未被访问,若存在则从它开始再进行一轮渲染
        for (int i = 0; i < graph.length; i++) {
            if (grep[i] != 0) {
                continue;
            }
            grep[i] = 1;
            queue.offer(i);
            while (!queue.isEmpty()) {
                int v = queue.poll();
                for(int j : graph[v]){
                    //如果当前顶点的某个邻接点已经被染过色,且颜色和当前节点相同,则证明此图不是二分图
                    if(grep[v] == grep[j]) {
                        return false;
                    }
                    if(grep[j] == 0) {
                        grep[j] = -grep[v];
                        queue.offer(j);
                    }

                }
            }
        }
        return true;
    }
}

你可能感兴趣的:(LeetCode,算法,leetcode,dfs,bfs)