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

DFS、BFS

回顾(C语言代码)
map[i][j]里记录的是i点和j点的连接关系

基本DFS:

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找从起点到终点的最短步数

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)

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

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)

//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坐标)

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-简单并查集类

单维点

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)坐标组成

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遍历图

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

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简洁形式

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

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

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

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;

    }
}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值