leetcode 图论专题——(dfs+bfs+并查集 回顾)

DFS、BFS

回顾(C语言代码)

map[i][j]里记录的是i点和j点的连接关系

基本DFS:

cpp 复制代码
int vis[101],n,map[101][101];
void dfs(int t)
{
    int i;
    vis[t]=1;
    for(i=0;i<n;i++)//找对t点所有有关联的点------"找路"
    {
        if(vis[i]!=0&&map[t][i]==1)//有连接边的点且没访问过
        {
            printf(" %d",i);
            dfs(i);
        }
    }
}
dfs(0);

通过BFS找从起点到终点的最短步数

cpp 复制代码
int n;
int map[1001][1001],vis[1001];
int quee[1001];//用个队列存着与当前点有连接边的点
int num[1001];//bfs下起点到各个点的步数
void bfs(int t)
{
    memset(vis,0,sizeof(vis));
    memset(num,1061109567,sizeof(num));
    memset(quee,0,sizeof(quee));
    int k,kk,i;
    k=0;
    kk=0;
    num[t]=0;//n到n 为0步
    quee[k++]=t;//在队列中放入起点
    vis[t]=1;
    while(kk<k)//队列不为空
    {
        int now=quee[kk++];//依次拿出队列中的节点
        for(i=1; i<=n; i++)//找路
        {
            if(vis[i]==0&&map[now][i]==1)//当前结点与i结点连通 && 没有遍历过
            {
                quee[k++]=i;
                vis[i]=1;		
                if(num[now]<num[i])//当前now点到i点距离更短,那就在num[now]的基础上+1
                    num[i]=num[now]+1;
            }
        }
    }
    
    if(num[n]==1061109567)//起点到终点距离是inf,说明到不了
        printf("NO\n");
    else printf("%d\n",num[n]);
}

在二维图迷宫里从起点到终点的路径数(dfs)

(这题需要注意的是,每次移动并不总是向着接近终点的放心走,可以在不重复的前提下乱窜,所以导致有很多条路)

cpp 复制代码
int n,m,num;
int map[11][11],vis[11][11];
void dfs(int x,int y)
{
    int i,tx,ty;
    int next[4][2]={{1,0},{-1,0},{0,1},{0,-1}};//下上右左 4种移动方式
   	vis[x][y]=1;
    if(x==n&&y==m)
    {
        num++;//每次到终点计数
        return ;
    }
    for(i=0;i<4;i++)//与for(0,n)一样都是去判断相连的点即"找路"
    {
        tx=x+next[i][0];//每次移动
        ty=y+next[i][1];
        if(tx<1||tx>n||ty<1||ty>m)//是否越界
        {
            continue;
        }
        if(vis[tx][ty]==0&&map[tx][ty]==0)//当前点可以走且没走过
        {
            vis[tx][ty]=1;
            dfs(tx,ty);
            vis[tx][ty]=0;//释放
        }
    }
}

dfs(1,1);//起点(1,1) 终点(n,m)

在二维图里从起点到终点的最短步数(bfs)

cpp 复制代码
//C写法
struct node
{
    int x,y,time;
} quee[101],now,t;//队列要记录xy坐标和到这点的步数
char map[16][16];
int n,m,vis[16][16];
void bfs(int x0,int y0)
{
    int i,k,kk;
    int next[4][2]= {{0,1},{0,-1},{1,0},{-1,0}};
    memset(quee,0,sizeof(quee));
    vis[x0][y0]=1;
    t.x=x0;t.y=y0;t.time=0;
    quee[0]=t;//起点信息放队列
    
    k=1;kk=0;
    while(kk<k)
    {
        now=quee[kk++];
        if(map[now.x][now.y]=='Y')//到终点
        {
            printf("%d\n",now.time);
            return ;
        }
        for(i=0; i<4; i++)//二维找路
        {
            t.x=now.x+next[i][0];
            t.y=now.y+next[i][1];
            if(t.x<0||t.x>=n||t.y<0||t.y>=m)
            {
                continue;
            }
            if(vis[t.x][t.y]==0&&map[t.x][t.y]!='#')//可以通行
            {
                t.time=now.time+1;
                quee[k++]=t;
                vis[t.x][t.y]=1;
            }
        }
    }
    printf("-1\n");
}

bfs(i1,j1);//起点传进去

不定义结构体存节点的x,y坐标,而是将坐标转化为m*i+j的形式(java其实可以将结点定义成类,类里有x,y坐标)

java 复制代码
int n = grid.length, m = grid[0].length;
int next[][] = {{1,0},{0,1},{-1,0},{0,-1}};
ArrayDeque<Integer> que = new ArrayDeque<Integer>();
int[] nums = new int[n*m+10];//记录从起点到当前点的最短距离
while(!que.isEmpty()){
  int t=que.poll();
  int x=t/m,y=t%m;//得到点的坐标
  int tx,ty;//下一步的坐标
  System.out.println(x+","+y);
  for(int i=0;i<4;i++){//四个方向
     tx=x+next[i][0];
     ty=y+next[i][1];
     if(tx<0||tx>=n||ty<0||ty>=m) continue;//超出边界
     if(grid[tx][ty]==1){//新鲜橙子
        grid[tx][ty]=2;
        System.out.println(x+","+y+":"+tx+","+ty);
        que.offer(tx*m+ty);
        nums[tx*m+ty]=nums[x*m+y]+1;
        maxx=Math.max(maxx,nums[tx*m+ty]);
   	}
  }
}

java-简单并查集类

单维点

java 复制代码
class UnionFind {
    private int[] parent;//记录祖先节点是谁
    private int[] rank;//统计此节点做了几次祖先了

    public UnionFind(int n) {//初始化
        parent = new int[n];//new空间
        for (int i = 0; i < n; i++) {
            parent[i] = i;//先让所有点的祖先是自己
        }
        rank = new int[n];
    }

    public void union(int x, int y) {//合并
        int rootx = find(x);
        int rooty = find(y);
        if (rootx != rooty) {//谁rank大谁做祖先
            if (rank[rootx] > rank[rooty]) {
                parent[rooty] = rootx;
            } else if (rank[rootx] < rank[rooty]) {
                parent[rootx] = rooty;
            } else {
                parent[rooty] = rootx;
                rank[rootx]++;
            }
        }
    }

    public int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);//找到并和祖先直接相连
        }
        return parent[x];//返回祖先
    }
}

二维图,点由(x,y)坐标组成

java 复制代码
class UnionFind {
        int[] parent;
        int[] rank;

        public UnionFind(char[][] grid) {//初始化
            count = 0;
            int m = grid.length;
            int n = grid[0].length;
            parent = new int[m * n];
            rank = new int[m * n];
            for (int i = 0; i < m; ++i) {
                for (int j = 0; j < n; ++j) {
                    if (grid[i][j] == '1') {
                        parent[i * n + j] = i * n + j;//将坐标(i,j)以数组下标的形式
                    }
                    rank[i * n + j] = 0;
                }
            }
        }

        public int find(int i) {
            if (parent[i] != i)
            	parent[i] = find(parent[i]);
            return parent[i];
        }

        public void union(int x, int y) {
            int rootx = find(x);
            int rooty = find(y);
            if (rootx != rooty) {
                if (rank[rootx] > rank[rooty]) {
                    parent[rooty] = rootx;
                } else if (rank[rootx] < rank[rooty]) {
                    parent[rootx] = rooty;
                } else {
                    parent[rooty] = rootx;
                    rank[rootx]++;
                }
            }
        }
    }

leetcode:

200. 岛屿数量

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 :

输入:grid = [

["1","1","0","0","0"],

["1","1","0","0","0"],

["0","0","1","0","0"],

["0","0","0","1","1"]

]

输出:3

关键:通过基础二维图dfs遍历图

java 复制代码
class Solution {
    int[][] vis=new int[303][303];
    int n,m;
    void dfs(char[][] map,int x,int y){
        int i,tx,ty;
        int[][] next={{1,0},{-1,0},{0,1},{0,-1}};//下上右左 4种移动方式
        vis[x][y]=1;

        for(i=0;i<4;i++){
            tx=x+next[i][0];
            ty=y+next[i][1];
            if(tx<0||tx>=n||ty<0||ty>=m){
                continue;
            }
            if(vis[tx][ty]==0&&map[tx][ty]=='1'){
                vis[tx][ty]=1;
                dfs(map,tx,ty);
            }
        }
    }
    public int numIslands(char[][] grid) {
        n=grid.length;
        m=grid[0].length;
        int cnt=0;
        //对图中所有没访问过的点做dfs
       for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            if(vis[i][j]==0&&grid[i][j]=='1'){
                dfs(grid,i,j);
                cnt++;//每次从新起点开始dfs就是一个独立的岛屿
            }
        }
       }
       return cnt;
        
    }
}

还可以并查集,将相邻且为1的在并查集里合并,最后统计由几个祖先节点。

994. 腐烂的橘子

在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一:

值 0 代表空单元格;

值 1 代表新鲜橘子;

值 2 代表腐烂的橘子。

每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。

返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1 。

输入:grid = [[2,1,1],[1,1,0],[0,1,1]]

输出:4

java 复制代码
class Solution {
    public int orangesRotting(int[][] grid) {
        int n = grid.length, m = grid[0].length;
        int next[][] = {{1,0},{0,1},{-1,0},{0,-1}};
        ArrayDeque<Integer> que = new ArrayDeque<Integer>();
        int[] nums = new int[n*m+10];//记录从起点到当前点的最短距离
        //找腐烂橙子放队列里
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                if(grid[i][j]==2){
                    que.offer(i*m+j);//将这个点的坐标信息放进去
                    nums[i*m+j]=0;
                }
            }
        }
        int maxx=0;
        while(!que.isEmpty()){
            int t=que.poll();
            int x=t/m,y=t%m;//得到点的坐标
            int tx,ty;//下一步的坐标
            System.out.println(x+","+y);
            for(int i=0;i<4;i++){//四个方向
                tx=x+next[i][0];
                ty=y+next[i][1];
                if(tx<0||tx>=n||ty<0||ty>=m) continue;//超出边界
                if(grid[tx][ty]==1){//新鲜橙子
                    grid[tx][ty]=2;
                    System.out.println(x+","+y+":"+tx+","+ty);
                    que.offer(tx*m+ty);
                    nums[tx*m+ty]=nums[x*m+y]+1;
                    maxx=Math.max(maxx,nums[tx*m+ty]);
                }
            }
        }

        //看看图里还有新鲜橙子吗
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                if(grid[i][j]==1) return -1;
            }
        }
        return maxx;
    }
}

拓扑排序问题

一个图是否存在合法拓扑序列的充要条件: 有向,无环。

找是否存在拓扑序列:找入度为0的顶点,删除出边(与它相连的点入度-1)

C简洁形式

cpp 复制代码
for(i=1;i<=n;i++)//计找n次入度为0的点
{      //n次全有入度为0的点则无环
  f=0;
  for(j=1;j<=n;j++)
  {
    if(in[j]==0&&!vis[j])
    {
      vis[j]=1;
      f=1;//有 入度为0的点
      for(k=1;k<=n;k++)
      {
          if(map[j][k]&&!vis[k])
          {
              in[k]--;
          }
      }
      break;//找到1个入度为0的点,i+1进入下一循环
    }
  }
  if(f==0) break;
}
if(f==0) printf("NO\n");
else printf("YES\n");

207. 课程表

你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。

在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。

例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。

请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

numCourses =2 prerequisites =[[1,0],[0,1]]

输出:false

numCourses =5 prerequisites =[[1,4],[2,4],[3,1],[3,2]]

输出:true

numCourses =5 prerequisites =[[1,2],[2,3],[3,1]]

输出:false

本质就是上述拓扑排序问题,要求有向无环

java 复制代码
class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        //有向无环图
        int n=prerequisites.length;
        int[] in=new int[numCourses];
        for(int i=0;i<n;i++){
            in[prerequisites[i][1]]++;//记录结点入度
        }
        int f=0;
        int[] vis=new int[numCourses];
        for(int i=0;i<numCourses;i++)//计找n次入度为0的点
        {      //n次全有入度为0的点则无环
            f=0;
            for(int j=0;j<numCourses;j++)
            {
                if(in[j]==0&&vis[j]==0)
                {
                    vis[j]=1;
                    f=1;//有 入度为0的点
                    for(int k=0;k<n;k++)
                    {
                        if(prerequisites[k][0]==j&&vis[prerequisites[k][1]]==0)//没访问过且与j有链接
                        {
                            in[prerequisites[k][1]]--;
                        }
                    }
                    break;//找到1个入度为0的点,i+1进入下一循环
                }
            }
            if(f==0) break;
        }
        if(f==0) return false;
        else return true;
    }
}

1559. 二维网格图中探测环

给你一个二维字符网格数组 grid ,大小为 m x n ,你需要检查 grid 中是否存在 相同值 形成的环。

一个环是一条开始和结束于同一个格子的长度 大于等于 4 的路径。对于一个给定的格子,你可以移动到它上、下、左、右四个方向相邻的格子之一,可以移动的前提是这两个格子有 相同的值 。

同时,你也不能回到上一次移动时所在的格子。比方说,环 (1, 1) -> (1, 2) -> (1, 1) 是不合法的,因为从 (1, 2) 移动到 (1, 1) 回到了上一次移动时的格子。

如果 grid 中有相同值形成的环,请你返回 true ,否则返回 false 。

示例 :

输入:grid = [["c","c","c","a"],["c","d","c","c"],["c","c","e","c"],["f","c","c","c"]]

输出:true

java 复制代码
class Solution {
    int[][] vis = new int[505][505];
    int flag;
    int n,m;
    
    void dfs(char[][] map,int x,int y,int xx,int yy)//x,y是当前点坐标
{                                 //xx,yy是x,y,前一步点的坐标
    vis[x][y]=1;
    if(flag==1) return ;
    /* (x,y)向上下左右移动 */
    if(x-1>=0&&map[x-1][y]==map[x][y]) //向上存在且同色
    {
        if(vis[x-1][y]==1&&(x-1!=xx||y!=yy)) 通过下一步(x-1,y)不是上一步(xx,yy),但这个下一步曾经走过,就说明成环了
        {                                  
            flag=1;
        }
        else if(vis[x-1][y]==0)  //没形成环就继续遍历
            dfs(map,x-1,y,x,y);
    }
    if(y+1<m&&map[x][y+1]==map[x][y])//向右存在且同色
    {
        if(vis[x][y+1]==1&&(x!=xx||y+1!=yy))
        {
            flag=1;
        }
        else if(vis[x][y+1]==0)
            dfs(map,x,y+1,x,y);
    }
    if(x+1<n&&map[x+1][y]==map[x][y])//向下存在且同色
    {
        if(vis[x+1][y]==1&&(x+1!=xx||y!=yy))
        {
            flag=1;
        }
        else if(vis[x+1][y]==0)
            dfs(map,x+1,y,x,y);
    }
    if(y-1>=0&&map[x][y-1]==map[x][y])//向左存在且同色
    {
        if(vis[x][y-1]==1&&(x!=xx||y-1!=yy))
        {
            flag=1;
        }
        else if(vis[x][y-1]==0)
            dfs(map,x,y-1,x,y);
    }
}

    public boolean containsCycle(char[][] grid) {
        flag=0;
        n=grid.length;
        m=grid[0].length;
        for(int i=0; i<n; i++)
        {
            for(int j=0; j<m; j++)
            {
                if(vis[i][j]==0)//对每个没访问过的点都进行成环dfs测试
                {
                    dfs(grid,i,j,i,j);
                }
                if(flag==1)
                    break;
            }
            if(flag==1) break;
        }
        if(flag==1)return true;
        else return false;

    }
}

官方解,时间复杂度更低的使用并查集,通过连通性 判断网格中是否有相同值形成的环,连通性问题可以使用并查集解决

(没看明白)

相关推荐
LNTON羚通5 分钟前
摄像机视频分析软件下载LiteAIServer视频智能分析平台玩手机打电话检测算法技术的实现
算法·目标检测·音视频·监控·视频监控
弗拉唐37 分钟前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi771 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
少说多做3431 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀1 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员
哭泣的眼泪4082 小时前
解析粗糙度仪在工业制造及材料科学和建筑工程领域的重要性
python·算法·django·virtualenv·pygame
清炒孔心菜2 小时前
每日一题 LCR 078. 合并 K 个升序链表
leetcode
蓝黑20202 小时前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea
Ysjt | 深2 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++