图论算法小结与题解

图论

797. 所有可能的路径(DFS)

题目描述

给你一个有 n 个节点的 有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序

graph[i] 是一个从节点 i 可以访问的所有节点的列表(即从节点 i 到节点 graph[i][j]存在一条有向边)。

示例 1:

输入:graph = [[1,2],[3],[3],[]]
输出:[[0,1,3],[0,2,3]]
解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3

示例 2:

输入:graph = [[4,3,1],[3,2,4],[3],[4],[]]
输出:[[0,4],[0,3,4],[0,1,3,4],[0,1,2,3,4],[0,1,4]]

解析:

打印从起点到终点的所有路径,很显然需要从其一起点沿着路径一直遍历到最深处,因此采用dfs,采用dfs需要考虑好dfs的具体参数,以及参数有什么用,以及dfs结束条件和得到答案的条件

参数确定:

  • 对于本题,因为是从0走到n-1,那么需要一个u来表示当前走到了哪个节点
  • 因为要保存过程做所需要的路径,所以还需要一个路径集合,(一般dfs参数如果不想要放到参数中,还可以放在全局变量中来进行,这样dfs参数就少一点
  • 参数中需注意,我们在得到一组解之后需要对路径进行复制,不能直接将path放入ans中,因为是引用类型

结束条件:和一般dfs和树等操作一样,当出现越界等情况就直接返回

答案:当u(当前节点)为最后一个节点n-1

代码

class Solution {
    private LinkedList<Integer> path=new LinkedList<>();
    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
        List<List<Integer>> ans = new ArrayList<>();
        int n=graph.length;
        path.add(0);
        dfs(0,ans,n,graph);
        return ans;

    }


    //u表示那个点
    private void dfs(int u, List<List<Integer>> ans,int n,int[][] graph) {
        if (u>=n)return;

        if (u==n-1){
            LinkedList<Integer> path1 = new LinkedList<>(path);
            ans.add(path1);

        }
        for (int next : graph[u]) {
            path.add(next);
            dfs(next,ans,n,graph);
            path.removeLast();
        }
     }
}

岛屿问题

200. 岛屿数量

题目描述

方法一:

DFS:本体使用dfs的话,作用就是将dfs的过程中将走过的路径进行标记,即当为陆地时继续dfs并且将该位置标记为已走过

所以格子有三种状态:

  • 0:水
  • 1:陆地
  • 2:已经走过的陆地

遍历当当前各自值为1的时候进行dfs,然后dfs完就代表以该格子的陆地块已经标记玩了,那么陆地板块就+1,最终返回答案

class Solution {
    //方法一:dfs
    //四个方向

    private int[] drict=new int[]{-1,0,1,0,-1};
    private int row,col;
    char[][] grid;
    public int numIslands(char[][] g) {
        grid=g;
        int ans=0;
        row=grid.length;
        col=grid[0].length;
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                if (grid[i][j]=='1'){
                    dfs(i,j);
                    ans++;

                }
            }
        }
        return ans;
    }

    private void dfs(int x, int y) {
        //越界
        if (x<0||x>=row||y<0||y>=col){
            return;
        }
        if (grid[x][y]!='1') return;
        //0:水
        //1:没遍历过的陆地
        //2:已经遍历过的陆地
        if (grid[x][y]=='1') grid[x][y]='2';

        for (int i = 0; i < 4; i++) {
            int newx=x+drict[i];
            int newy=y+drict[i+1];
            dfs(newx,newy);
        }


    }
}
方法二:并查集

首先我们常用的并查集的father数组都是一维的,所以这里需要将二维的坐标转化为一维坐标,本题只需要在并查集模板上稍微改造一下即可,即我们在初始化father数组时如果遇到1那么就set集合+1(即开始时默认每个陆地都是一个岛屿),然后每次可以合并时就将set--,最后返回set就是剩下的岛屿数

class Solution {
    //并查集所用
    public int MAXN=310;
    public int MAXM=310;
    public int[] father=new int[MAXM*MAXN];
    public int row,col;
    int set=0;
    char[][] grid;
    public void build(){

        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                int index=transform(i,j);
                father[index]=index;
                if (grid[i][j]=='1'){
                    set++;
                }

            }
        }
    }
    public int find(int x){
        if (x!=father[x]){
            father[x]=find(father[x]);
        }
        return father[x];
    }
    public void union(int x,int y){
        int fx=find(x);
        int fy=find(y);
        if (fy!=fx){
            set--;
            father[fx]=fy;
        }

    }
    //二维坐标转一维
    public int transform(int x,int y){
        return x*col+y;

    }
    //方法二:并查集
    public int numIslands(char[][] g) {
        grid=g;
        row=g.length;
        col=g[0].length;
        build();
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {

                //往右
                if (j+1<col&&g[i][j]=='1'&&g[i][j+1]=='1'){
                    int index1=transform(i,j);
                    int index2=transform(i,j+1);
                    union(index1,index2);
                }
                //往下
                if (i+1<row&&g[i][j]=='1'&&g[i+1][j]=='1'){
                    int index1=transform(i,j);
                    int index2=transform(i+1,j);
                    union(index1,index2);
                }

            }

        }
        return set;


    }
}

695. 岛屿的最大面积

题目描述

class Solution {

    //方向
    int[] direct = new int[]{-1, 0, 1, 0, -1};
    int[][] grid;
    int n, m;

    public int maxAreaOfIsland(int[][] g) {
        int ans = 0;
        grid = g;
        n = grid.length;
        m = grid[0].length;
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                if (grid[i][j] == 1) {
                    ans = Math.max(ans, dfs(i, j));
                }

            }

        }
        return ans;


    }

    private int dfs(int x, int y) {
        if (x < 0 || x >= n || y < 0 || y >= m) return 0;
        if (grid[x][y] != 1) return 0;
        if (grid[x][y] == 1) grid[x][y] = 2;
        return 1 + dfs(x + 1, y)
                + dfs(x - 1, y)
                + dfs(x, y + 1)
                + dfs(x, y - 1);
    }
解析

这题和岛屿数量解法相同,唯一不同的时,岛屿数量求得是最终岛屿得总个数,而这个球的是最大大于面积

即岛屿数量得dfs主要作用是寻找同一块岛屿,然后给遍历过的岛屿标记

而求面积则需要在遍历过程中每经过一块地就+1,因此dfs需要返回对应的值

463. 岛屿的周长

解析

观察可知,周长就是岛屿的边界处,即越界或者下一步是水那么这一条边就是边界属于周长,因此我们在dfs时在越界处和下一步是水是返回1即可,同时核心走过的陆地同样标记为2

class Solution {

    int[][] grid;
    int n,m;
    int[] direct=new int[]{-1,0,1,0,-1};
    public int islandPerimeter(int[][] g) {
        grid=g;
        n=grid.length;
        m=grid[0].length;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (grid[i][j]==1) return dfs(i,j);

            }

        }
        return 0;


    }

    private int dfs(int x, int y) {
        if (x<0||x>=n||y<0||y>=m) return 1;
        if (grid[x][y]==0) return 1;
        if (grid[x][y]==2) return 0;

        //==1时候
        grid[x][y]=2;
        return dfs(x+1,y)
                +dfs(x-1,y)
                +dfs(x,y-1)
                +dfs(x,y+1);
    }


}

827. 最大人工岛

题目描述

解析

总结题目:有一个将水格子变成陆地的机会,然后需要找通过变化后的最大陆地面积

思路:

首相这里需要考虑到两个陆地可能通过这个变化各自连接成一个陆地,所以首先我们需要

  • 先进行一遍dfs这个dfs的作用就是
    • 1、给遍历过的同一陆地进行编号
    • 2、需要返回这个陆地的面颊
    • dfs完后将边号作为key,面积作为value存储来map中
  • 的到每个边号陆地的面积map后,我们需要再一次遍历图,找到图中值为0,即为水的地方将其来拼接面积,我们再进行另一个dfs
    • 这个dfs主要就是为了找到这个水格子相邻的陆地边号,注意,这个边号需要进行去重操作,防止陆地通过水格子自己连接自己的情况
    • 之后进行循环取最大值即可
class Solution {
    int[][] grid;
    int n,m;
    public int largestIsland(int[][] g) {
        grid=g;
        n=grid.length;
        m=grid[0].length;
        //第一个dfs为每个岛屿进行边号同时返回每个岛屿的面积,放在map中存储

        //岛屿边号从2开始,因为0,1已经有了自己的含义
        int index=2;
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (grid[i][j]==1){
                    int erea=dfs(i,j,index);
                    map.put(index++,erea);
                }
            }
        }
        //看看有无特殊情况
        if (index==2){
            //说明全是水
            return 1;
        }
        int ans=0;

        //第二个dfs用来模拟将水换位陆地后找最大面积
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (grid[i][j]==0){
                    //dfs得到相邻的去重后的集合
                    Set<Integer> set=dfs2(i,j);
                    int res=1;
                    for (Integer tag : set) {
                        res+=map.getOrDefault(tag,0);

                    }
                    ans=Math.max(ans,res);
                }

            }

        }
        //如果ans=0那么说明全是陆地没有水
        return ans==0?m*n:ans;

    }

    //定义四个方向
    int[] direct =new int[]{-1,0,1,0,-1};
    private Set<Integer> dfs2(int x, int y) {
        HashSet<Integer> set = new HashSet<>();
        for (int i = 0; i < 4; i++) {
            int newx=x+direct[i];
            int newy=y+direct[i+1];

            if (newx>=0&&newx<n&&newy>=0&&newy<m){
                set.add(grid[newx][newy]);
            }

        }
        return set;
    }

    private int dfs(int x, int y, int index) {
        if (x<0||x>=n||y<0||y>=m) return 0;
        if (grid[x][y]!=1) return 0;
        //到这就是陆地1了
        grid[x][y]=index;
        //计算面积
        return 1+dfs(x+1,y,index)
                +dfs(x-1,y,index)
                +dfs(x,y-1,index)
                +dfs(x,y+1,index);
    }


}

 

dijstra系列

模版

求单源最短路径

题目位置登录 - Luogu Spilopelia

import java.io.*;
import java.util.Arrays;
import java.util.PriorityQueue;

public class Main {
    public static int MAXN = 100010;
    public static int MAXM = 200010;
    //建图
    public static int[] head = new int[MAXN];
    public static int[] next = new int[MAXM];
    public static int[] to = new int[MAXM];
    public static int[] weigth = new int[MAXM];
    public static int cnt = 1;

    //dijstra用
    public static int[] distance = new int[MAXN];
    public static boolean[] visited = new boolean[MAXN];
    public static PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> a[1] - b[1]);

    //初始化
    public static void build(int n) {
        cnt = 1;
        Arrays.fill(head, 1, 1 + n, 0);
        Arrays.fill(distance, 1, 1 + n, Integer.MAX_VALUE);
        Arrays.fill(visited, 1, 1 + n, false);
    }

    //建图
    public static void buildEage(int u, int v, int w) {
        next[cnt] = head[u];
        to[cnt] = v;
        weigth[cnt] = w;
        head[u] = cnt++;
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StreamTokenizer in = new StreamTokenizer(br);
        PrintWriter out = new PrintWriter(System.out);

        in.nextToken();
        int n = (int) in.nval;
        in.nextToken();
        int m = (int) in.nval;
        in.nextToken();
        int s = (int) in.nval;

        //初始化
        build(n);
        //建图

        for (int j = 1; j <= m; j++) {
            in.nextToken();
            int u = (int) in.nval;
            in.nextToken();
            int v = (int) in.nval;
            in.nextToken();
            int w = (int) in.nval;
            buildEage(u, v, w);
        }


        //开始dj
        heap.offer(new int[]{s, 0});
        distance[s]=0;

        while (!heap.isEmpty()) {
            //弹出最小
            int[] poll = heap.poll();
            int cur = poll[0];
            int w = poll[1];
            if (visited[cur] == true) continue;
            //设为已访问
            visited[cur] = true;

            //消除影响
            for (int ei = head[cur]; ei != 0; ei = next[ei]) {
                int nex = to[ei];
                int nw = weigth[ei];
                //是否能更新
                if (!visited[nex] && w + nw < distance[nex]) {
                    heap.offer(new int[]{nex, w + nw});
                    distance[nex] = w + nw;
                }
            }

        }
        //打印
        for (int i = 1; i <= n; i++) {
            out.print(distance[i] + " ");
        }
        out.flush();
        out.close();
        br.close();


    }
}

1631. 最小体力消耗路径

题目描述

class Solution {
    //用dj变形
    //通常的DJ求的是单元最短路径
    //而这个题目相当于要求最大中的最小:即从所有导终点的路径的高度差最大值中找最小值
    //因此,我们distance数组中就保存最大的最小,即每次从堆中弹出来后,取弹出来权值和高度差取最大值然后和下一个点的distance作比较
    //按照dj思路进行操作
    public int minimumEffortPath(int[][] heights) {
        int n=heights.length;
        int m=heights[0].length;

        int[][] distacne=new int[n][m];
        boolean[][] visited=new boolean[n][m];
        //堆中含义:从远点(0,0)到(i,j)的最短路径为2
        //0:i
        //1:j
        //2:权值
        PriorityQueue<int[]> heap = new PriorityQueue<>((a,b)->a[2]-b[2]);
        for (int i = 0; i < n; i++) {
            Arrays.fill(distacne[i],Integer.MAX_VALUE);
            Arrays.fill(visited[i],false);
        }



        heap.offer(new int[]{0,0,0});
        //定义四个方向
        int[] direct=new int[]{-1,0,1,0,-1};
        distacne[0][0]=0;
        while (!heap.isEmpty()){
            //弹出
            int[] poll = heap.poll();
            int x=poll[0];
            int y=poll[1];
            int w=poll[2];
            if (visited[x][y]) continue;
            if (x==n-1&&y==m-1) return w;
            visited[x][y]=true;
            for (int i = 0; i < 4; i++) {
                int newx=x+direct[i];
                int newy=y+direct[i+1];
                if (newx<0||newx>=n||newy<0||newy>=m) continue;
                int mw=Math.max(w,Math.abs(heights[newx][newy]-heights[x][y]));
                if (!visited[newx][newy]&&mw<distacne[newx][newy]){
                    distacne[newx][newy]=mw;
                    heap.offer(new int[]{newx,newy,mw});
                }
            }
        }
        return -1;
    }
}

动态规划

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值