图论问题建模

算法笔试中图论问题的书写 一般不用我们先创建图,题目一般都会给出图,我们直接在给定的数据结构上进行问题的求解。

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
我们不能将节点分割成两个独立的子集。
注意:

graph 的长度范围为 [1, 100]。
graph[i] 中的元素的范围为 [0, graph.length - 1]。
graph[i] 不会包含 i 或者有重复的值。
图是无向的: 如果j 在 graph[i]里边, 那么 i 也会在 graph[j]里边。

//模板
class Solution {
    public boolean isBipartite(int[][] graph) {

    }
}

算法解答:二分图的判断在之前的图的深度优先遍历和图的广度优先遍历中都有过代码书写。这里唯一不同的是,题目给的二维数组就是一个邻接表,因此我们不用自己手动建立一个图模型就可以直接在数组上进行图的遍历操作。

// 已经以临接表的形式给出
public class LeetCode785 {
     
    private int[][] graph;
    private boolean[] visited;
    private int[] colors;

    public boolean isBipartite(int[][] graph) {
     
        this.graph = graph;
        visited = new boolean[graph.length];
        colors = new int[graph.length];
        for (int v = 0; v < graph.length; v++) {
     

            if (!visited[v]) {
     
                if (!dfs(v, 0))
                    return false;
            }
        }
        return true;
    }

    private  boolean dfs(int  s , int color){
     
        visited [s]  = true;
        colors[s]  = color ;

        for(int  w : graph[s]){
     
            if(!visited[w]){
     
                if(!dfs(w, 1-color)){
     
                    return  false;
                }
            }else  if(colors[s]==colors[w])
                return false;
        }
        return  true;
    }
}

图的建模求连通分量中顶点最大个数 LeetCode 695号岛屿的最大面积

题目信息
给定一个包含了一些 0 和 1 的非空二维数组 grid 。

一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。

找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)

 

示例 1:

[[0,0,1,0,0,0,0,1,0,0,0,0,0],
 [0,0,0,0,0,0,0,1,1,1,0,0,0],
 [0,1,1,0,1,0,0,0,0,0,0,0,0],
 [0,1,0,0,1,1,0,0,1,0,1,0,0],
 [0,1,0,0,1,1,0,0,1,1,1,0,0],
 [0,0,0,0,0,0,0,0,0,0,1,0,0],
 [0,0,0,0,0,0,0,1,1,1,0,0,0],
 [0,0,0,0,0,0,0,1,1,0,0,0,0]]
对于上面这个给定矩阵应返回 6。注意答案不应该是 11 ,因为岛屿只能包含水平或垂直的四个方向的 1 。

示例 2:

[[0,0,0,0,0,0,0,0]]
对于上面这个给定的矩阵, 返回 0。


注意: 给定的矩阵grid 的长度和宽度都不超过 50。

通过题目我们可以发现,给我们的并不是一个邻接表或者邻接矩阵表示的图,而是一个二维数组,数组中的每一个元素都是一个顶点。那么,我们就可以想一下,如何把一个二维数组映射到一维中。(后面我们可看到不进行映射也是可以解答的)。

二维映射到一维, 把每一行的首尾连接起来。 比如,(2,1) 前面有2行,左边有1列, 2*13+1 13是一行有多少元素。 于是 (x, y) 就可以映射为 X * C + Y 。

一维映射为二维, 如果一个顶点的一维坐标是27, 那么我们就可以用 27/13 = 2 27%13 =1 这是和二维映射为1维正好相反的过程。x = v/C y = v%C C是原来的二维数组有多少列。

四联通,找到一个点上下左右 四个联通的点, dirs = [[-1,0] , [0,1] , [1, 0], [0,-1]] ; 向左,向上,向右,向下。 顺时针旋转。 第一个维度是在行上发生的变化,第二个维度是在列上发生的变化。

for(d  = 0 ;d < 4 ;d++ ){
     
	nextx = x  + dirs[d][0];
	nexty = y  + dirs[d][1] ;
}

有些问题还关注平面上的8联通 (米字型) ,或者三维空间的8联通(上下 左右 前后)。

在下面的代码中,我使用hashset 数组重新构建了一个邻接表,然后在这个邻接表上进行图的深度优先遍历。

import java.util.HashSet;

// 把题目给定的二维数组转换成常见的图的邻接表 然后在邻接表中进行深度优先遍历
public class Solution {
     
    private int R, C;
    private int[][] grid;
    private boolean[] visited;
    private HashSet<Integer>[] g;
    private int[][] dirs = {
     {
     -1, 0}, {
     0, -1}, {
     1, 0}, {
     0, 1}}; // 方向

    public int maxAreaOfIsland(int[][] grid) {
     
        this.grid = grid;
        R = grid.length;
        C = grid[0].length;
        int res = 0;
        visited = new boolean[R * C];
        g = constuct(); // 把二维数组构建成邻接表表示的图模型
        for (int v = 0; v < R * C; v++) {
     
            int x = v / C;
            int y = v % C;
            if (!visited[v] && grid[x][y] == 1) {
     
                res = Math.max(res, dfs(v));
            }
        }

        return res;
    }
	//深度优先遍历求解 连通分量的顶点个数 核心算法
    private int dfs(int v) {
     
        visited[v] = true;
        int count = 1;
        for (int w : g[v])
            if(!visited[w])
                count += dfs(w) ;

        return  count ;
    }

    private HashSet<Integer>[] constuct() {
     
        HashSet<Integer>[] G = new HashSet[R * C];
        for (int i = 0; i < R * C; i++)
            G[i] = new HashSet<>();

        for (int v = 0; v < R * C; v++) {
     
            int x = v / C; // 将一维转换为2维
            int y = v % C;
            if (grid[x][y] == 1) {
      // 如果当前这个点是一个陆地

                for (int d = 0; d < 4; d++) {
      //搜索它的四个方向
                    int newx = x + dirs[d][0];
                    int newy = y + dirs[d][1];
                    int next = newx * C + newy; // 将二维转换为1维
					//下面的if中的两个条件不能位置互换,这是因为有些坐标是越界的 需要先判断是否越界 然后再判断数值是否为1 
                    if (inArea(newx, newy) && grid[newx][newy] ==1) {
      // 找到一对邻接点
                        G[v].add(next);
                        G[next].add(v);
                    }
                }
            }
        }
        return G;
    }
    //  判断一个点坐标是否在二维数组中没有越界
    private boolean inArea(int x, int y) {
     
        return x >= 0 && x < R && y >= 0 && y < C;
    }
}

我们是否可以不进行图的建立,而是直接在这个二维数组中进行深度优先遍历,因为这个数组grid已经给了我们足够的信息。当然是可以的,而且更加简单和方便。 下面给出参考代码

//提交解答需要把LeetCode695 换成 Solution 
// 岛屿的最大面积 求解每一个连通分量的顶点个数的最大值
public class LeetCode695 {
     
    private int res; //最终返回的结果
    private int R, C; // 数组grid的行数和列数
    private int[][] graph; // 指向 grid 数组,方便在后面的函数进行调用
    private boolean[][] visited; // 访问标记数组
    private int[][] dirs = {
     {
     -1, 0}, {
     0, -1}, {
     1, 0}, {
     0, 1}}; // 方向数组

    public int maxAreaOfIsland(int[][] grid) {
     
        if (grid.length == 0)
            return 0;
        R = grid.length;
        C = grid[0].length;
        visited = new boolean[R][C];
        graph = grid;
        res = 0;

        for (int i = 0; i < R; i++) {
     
            for (int j = 0; j < C; j++) {
     

                if (!visited[i][j] && grid[i][j] == 1) {
      // 对每一个连通分量进行访问并返回连通分量的顶点为1的点是数量

                    res = Math.max(res, dfs(i, j));
                }

            }
        }
        return res;
    }

    private int dfs(int x, int y) {
      
        visited[x][y] = true;
        int count = 1; // 当进如一次dfs 说明在二维数组中找到了一个值为1的点

        for (int d = 0; d < 4; d++) {
      //然后顺时针对(x,y)前后左右四个方向进行遍历
            int newx = x + dirs[d][0];
            int newy = y + dirs[d][1];//相邻的点需满足,在数组范围内,点是1 , 没有被访问过
            if (inArea(newx, newy) && graph[newx][newy] == 1 && !visited[newx][newy]) {
     
                count += dfs(newx, newy); //把dfs 返回的结果不断累加起来
            }
        }
        return count; //最后把当前累加的结果返回给递归的上一层
    }
    //判断坐标的范围是否是合法的
    private boolean inArea(int x, int y) {
     
        return x >= 0 && x < R && y >= 0 && y < C;
    }

}

你可能感兴趣的:(图论)