力扣HOT 100(图)

 图论

797. 所有可能的路径
 

为什么path先把索引加上,图这个数据结构的索引,包含了数据信息,所以索引到数据表再到索引这个过程。一般回溯索引没有涉及问题中的含义。

class Solution {
    List<Integer> path=new ArrayList<>();//Deque
    List<List<Integer>> result=new ArrayList<>();
    private void backTrack(int[][] graph,int i, int n)
    {
        if(i==n)
        {
            //以后都别直接add一个引用。多维数组时
            result.add(new ArrayList<>(path));
            return;
        }
        for(int j:graph[i]){
            path.add(j);
            backTrack(graph,j,n);
            //remove()参数是索引
            path.remove(path.size()-1);
        }
    }
    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
        path.add(0);
        backTrack(graph,0,graph.length-1);
        return result;
    }
}

用deque,只能赋值LinkedList

class Solution {
    Deque<Integer> path=new LinkedList<>();//Deque
    List<List<Integer>> result=new ArrayList<>();
    private void backTrack(int[][] graph,int i, int n)
    {
        if(i==n)
        {
            //以后都别直接add一个引用。多维数组时
            result.add(new ArrayList<>(path));
            return;
        }
        for(int j:graph[i]){
            //队列
            path.addLast(j);
            backTrack(graph,j,n);
            path.pollLast();
        }
    }
    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
        path.add(0);
        backTrack(graph,0,graph.length-1);
        return new ArrayList<>(result);
    }
}

98. 所有可达路径

邻接矩阵:

import java.util.*;


class Main{
    //图数据结构
    static Deque<Integer> path=new LinkedList<>();
    static List<List<Integer>> result=new ArrayList<>();

    private static void backTrack(int[][] graph,int i,int n){
        if(i==n){
            result.add(new ArrayList<>(path));
            return;
        }
        for(int j=1;j<=n;j++){
            if(graph[i][j]==1){
                path.add(j);
                backTrack(graph,j,n);
                path.removeLast();
            }
        }
    }
    
    public static void main(String[] args)
    {
        Scanner scan=new Scanner(System.in);
        int n=scan.nextInt();
        int[][] graph=new int[n+1][n+1];
        int m=scan.nextInt();
        for(int i=0;i<m;i++){
            int a=scan.nextInt();
            int b=scan.nextInt();
            graph[a][b]=1;
        }
        path.addFirst(1);
        backTrack(graph,1,n);
        if (result.size() == 0) {
        System.out.println(-1);
        } 
        else{
            for(List<Integer> list:result){
            for(int i=0;i<list.size();i++){
                System.out.print(list.get(i));
                if(i<list.size()-1){
                    System.out.print(" ");
                }
            }
            System.out.print("\n");
        }}
        scan.close();
    }
}

邻接表,因为数组里面不是基本类型,所以需要初始化一下。

对于多维数组(如int[][]),如果数组是基本类型,则Java会自动处理内层数组的初始化;如果数组是非基本类型,则内层数组也需要显式初始化。两次new两次箭头,

new int[][]一次把两个箭头生成。

由列表组成的数组(固定数量决定用这个)。所以是下面的类型。

import java.util.*;


class Main{
    //图数据结构
    static Deque<Integer> path=new LinkedList<>();
    static List<List<Integer>> result=new ArrayList<>();

    private static void backTrack(List<Integer>[] graph,int i,int n){
        if(i==n){
            result.add(new ArrayList<>(path));
            return;
        }
        for(int j:graph[i]){
            path.add(j);
            backTrack(graph,j,n);
            path.removeLast();

        }
    }

    public static void main(String[] args)
    {
        Scanner scan=new Scanner(System.in);
        int n=scan.nextInt();
        //n+1个ArrayList数组,new那里没有<>
        //List[] graph=new List[n+1];也可以
        List<Integer>[] graph=new ArrayList[n+1];//固定行,用数组
        int m=scan.nextInt();
        for(int i=0;i<=n;++i)
        {
            //二维必须包含最内层的new
            graph[i]=new ArrayList<>();
        }
        for(int i=0;i<m;i++){
            int a=scan.nextInt();
            int b=scan.nextInt();
            graph[a].add(b);
        }
        path.addFirst(1);
        backTrack(graph,1,n);
        if (result.isEmpty()) {
            System.out.println(-1);
        }
        else{
            for(List<Integer> list:result){
                for(int i=0;i<list.size();i++){
                    System.out.print(list.get(i));
                    if(i<list.size()-1){
                        System.out.print(" ");
                    }
                }
                System.out.print("\n");
            }}
        scan.close();
    }
}

99.岛屿数量

就是求连通图的数量。

看成m个节点和n个节点相连的图。

import java.util.*;
 
 class Main{
    static int[][] direct={{1,0},{0,1},{0,-1},{-1,0}};
    static int n,m;
    private static void backTrack(int[][] graph,boolean[][] visited,int x,int y){
        for(int[] d:direct)
        {
            int curX=x+d[0],curY=y+d[1];
            if(curX<0 || curX>=n ||curY<0 ||curY>=m)continue;
            if(!visited[curX][curY]&&graph[curX][curY]==1){
                visited[curX][curY]=true;
                backTrack(graph,visited,curX,curY);
                
            }
        }
    }
    public static void main(String[] args)
    {
        Scanner scan=new Scanner(System.in);
        n=scan.nextInt();
        m =scan.nextInt();
        int[][] graph=new int[n ][m];
        for(int i=0;i<n;++i){
            for(int j=0;j<m;++j){
                graph[i][j]=scan.nextInt();
            }
        }
        int count=0;
        boolean[][] visited=new boolean[n][m];
        for(int i=0;i<n;++i){
            for(int j=0;j<m;++j){
                if(visited[i][j]==false&&graph[i][j]==1){
                    visited[i][j]=true;
                    backTrack(graph,visited,i,j);
                    count++;
                }
            }
        }
        scan.close();
        System.out.println(count);
    }
}

把在里面的条件放在前面剪枝也可以:

if (visited[x][y] || grid[x][y] == 0) return; // 终止条件:访问过的节点 或者 遇到海水

bfs和上面,只是改遍历每个 连通分量 的方式,那么还是主函数每个节点都要判断要不要bfs,要的话说明到达新大陆,count++(在主函数操作而不是bfs)

import java.util.*;
 
 class Main{
     //一个pair一个点
    static class pair{
         int first;
         int second;
         public pair(int first,int second){
             this.first=first;
             this.second=second;
         }
     }
     //对应层次的左右孩子。四个邻居/孩子
    static int[][] direct={{1,0},{0,1},{0,-1},{-1,0}};
    static Deque<pair> queue=new LinkedList<>();//显式的队列,dfs隐式的栈
    static int n,m,count;
    private static void bfs(int[][] graph,boolean[][] visited,int x,int y){
        queue.addLast(new pair(x,y));
       // visited[x][y]=true;不必要,main里面已经有了
        while(!queue.isEmpty()){
            pair p = queue.pollFirst();
            int prex=p.first;
            int prey=p.second;
            for(int[] d:direct)
            {
                int curX=prex+d[0],curY=prey+d[1];
                if(curX<0 || curX>=n ||curY<0 ||curY>=m)continue;
                if(!visited[curX][curY]&&graph[curX][curY]==1){
                    visited[curX][curY]=true;
                    queue.addLast(new pair(curX,curY));//queue里面放已经访问的
                }
            }
        }
        
        
    }
    public static void main(String[] args)
    {
        Scanner scan=new Scanner(System.in);
        n=scan.nextInt();
        m =scan.nextInt();
        int[][] graph=new int[n ][m];
        for(int i=0;i<n;++i){
            for(int j=0;j<m;++j){
                graph[i][j]=scan.nextInt();
            }
        }
        boolean[][] visited=new boolean[n][m];
        queue.addLast(new pair(0,0));
        for(int i=0;i<n;++i){
            for(int j=0;j<m;++j){
                if(visited[i][j]==false&&graph[i][j]==1){
                    visited[i][j]=true;
                    bfs(graph,visited,i,j);//从(i,j)开始遍历。遍历完联通子图,下一次从新的连通子图开始
                    count++;
                }
            }
        }
        scan.close();
        System.out.println(count);
    }
}

dfs还有话说:

先污染后治理:

private void dfs(int x, int y) {
        if (x < 0 || x >= n || y < 0 || y >= m || grid[x][y]=='0' || visited[x][y]) return;
        visited[x][y] = true;

        for (int[] d : directs) {
            int nextX = x + d[0];
            int nextY = y + d[1];

            dfs(nextX, nextY);  //先污染后治理
        }
    }

下面这样也行但是时间长点:

private void dfs(int x, int y) {
        
        visited[x][y] = true;

        for (int[] d : directs) {
            int nextX = x + d[0];
            int nextY = y + d[1];
            if (nextX < 0 || nextX >= n || nextY < 0 || nextY >= m || grid[nextX][nextY]=='0'||visited[nextX][nextY] ) continue;
             dfs(nextX, nextY);  //先污染后治理
        }
    }

不用visited,bfs超时,dfs可以

class Pair {
    int first, second;
    public Pair(int x, int y) {
        this.first = x;
        this.second = y;
    }
}
// 在一些题解中,可能会把「已遍历过的陆地格子」标记为和海洋格子一样的 0,美其名曰「陆地沉没方法」,即遍历完一个陆地格子就让陆地「沉没」为海洋。这种方法看似很巧妙,但实际上有很大隐患,因为这样我们就无法区分「海洋格子」和「已遍历过的陆地格子」了。如果题目更复杂一点,这很容易出 bug。




class Solution {
    int n, m;
    //网格结构的 DFS 与二叉树的 DFS 最大的不同之处在于,遍历中可能遇到遍历过的结点。
    int[][] directs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
    char[][] grid;

    private void bfs(int x, int y) {
        Deque<Pair> q = new LinkedList<>();
        q.push(new Pair(x, y));
        grid[x][y]='2';
        while (!q.isEmpty()) {
            Pair curr = q.poll();
            int currX = curr.first;
            int currY = curr.second;

            for (int[] d : directs) {
                int nextX = currX + d[0];
                int nextY = currY + d[1];
                //超时
                if ( nextX < 0 || nextX >= n || nextY < 0 || nextY >= m ||grid[nextX][nextY]!='1') {
                    continue;
                }

                q.push(new Pair(nextX, nextY));
            }
        }
    }

    private void dfs(int x, int y) {
        
        grid[x][y]='2';

        for (int[] d : directs) {
            int nextX = x + d[0];
            int nextY = y + d[1];
            //可过
            if (nextX < 0 || nextX >= n || nextY < 0 || nextY >= m || grid[nextX][nextY]!='1') continue;
             dfs(nextX, nextY);   
        }
    }

    public int numIslands(char[][] grid) {
        this.grid=grid;
        n = grid.length;
        m = grid[0].length;
        int count=0;
         for(int i=0;i<n;++i){
            for(int j=0;j<m;++j){
                //没访问的陆地
                if(grid[i][j]=='1'  ){
                    bfs(i,j);
                    count++;
                }
            }
        }
        return count ;
    }
}

看看注释:

class Pair {
    int first, second;
    public Pair(int x, int y) {
        this.first = x;
        this.second = y;
    }
}
// 在一些题解中,可能会把「已遍历过的陆地格子」标记为和海洋格子一样的 0,美其名曰「陆地沉没方法」,即遍历完一个陆地格子就让陆地「沉没」为海洋。这种方法看似很巧妙,但实际上有很大隐患,因为这样我们就无法区分「海洋格子」和「已遍历过的陆地格子」了。如果题目更复杂一点,这很容易出 bug。




class Solution {
    int n, m;
    //网格结构的 DFS 与二叉树的 DFS 最大的不同之处在于,遍历中可能遇到遍历过的结点。
    boolean[][] visited;
    int[][] directs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
    char[][] grid;

    private void bfs(int x, int y) {
        Deque<Pair> q = new LinkedList<>();
        q.push(new Pair(x, y));
        visited[x][y] = true;

        while (!q.isEmpty()) {
            Pair curr = q.poll();
            int currX = curr.first;
            int currY = curr.second;

            for (int[] d : directs) {
                int nextX = currX + d[0];
                int nextY = currY + d[1];

                if ( nextX < 0 || nextX >= n || nextY < 0 || nextY >= m ||grid[nextX][nextY]=='0'|| visited[nextX][nextY]) {
                    continue;
                }

                q.push(new Pair(nextX, nextY));
                visited[nextX][nextY] = true;
            }
        }
    }

    private void dfs(int x, int y) {
        
        visited[x][y] = true;

        for (int[] d : directs) {
            int nextX = x + d[0];
            int nextY = y + d[1];
            if (nextX < 0 || nextX >= n || nextY < 0 || nextY >= m || grid[nextX][nextY]=='0'||visited[nextX][nextY] ) continue;
             dfs(nextX, nextY);   
        }
    }

    public int numIslands(char[][] grid) {
        this.grid=grid;
        n = grid.length;
        m = grid[0].length;
        int count=0;
        visited = new boolean[n][m];
        for(int i=0;i<n;++i){
            for(int j=0;j<m;++j){
                //陆地海洋、访问数组:成对出现在判断条件
                if(grid[i][j]=='1' &&!visited[i][j]){
                    dfs(i,j);
                    count++;
                }
            }
        }
        return count ;
    }
}

100. 岛屿的最大面积

这个有点奇怪的案例:

03a0c42351b1432b83c50617b94f6935.png

import java.util.*;

public class Main{
    static int maxArea,curArea;
    static int n,m;
    static boolean[][] visited;
    static int[][] direct = {{1,0},{0,1},{-1,0},{0,-1}};
    private static void bfs(int[][] graph,int x,int y)
    {
        for(int[] d:direct){
            int curx=x+d[0],cury=y+d[1];
            if(curx<0 ||curx>=n ||cury<0||cury>=m)continue;
            if(graph[curx][cury]==1 && !visited[curx][cury])
            {
                curArea++;
                visited[curx][cury]=true;
                //递归之前先把当前节点处理了
                bfs(graph,curx,cury);
            }
        }
    }
    public static void main(String[] args)
    {
        Scanner scan=new Scanner(System.in);
        n=scan.nextInt();
        m=scan.nextInt();
        int[][] graph=new int[n][m];
        for(int i=0;i<n;++i)
        {
           for(int j=0;j<m;++j)
            {
                graph[i][j]=scan.nextInt();
            } 
        }
        visited = new boolean[n][m];
        for(int i=0;i<n;++i)
        {
           for(int j=0;j<m;++j)
            {
                if(graph[i][j]==1 && !visited[i][j])
                {
                    curArea=1;
                    visited[i][j]=true;
                    //调用之前先把当前节点处理了
                    bfs(graph,i,j);
                    maxArea=Math.max(curArea,maxArea);
                }
            } 
        }
        System.out.println(maxArea);
    }
}

101. 孤岛的总面积

想用标志位代表不是孤岛就不统计了,但是出错:GPT也没有改对。

import java.util.*;

public class Main {
    static boolean[][] visited;
    static int totalArea, curArea;
    static int n, m, yes;
    static int[][] direct = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};

    public static void bfs(int[][] graph, int i, int j) {
        // 如果触及边界,设置标志为不是孤岛
        if (i == 0 || j == 0 || i == n - 1 || j == m - 1) {
            yes = 1;
            return;
        }
        // 遍历4个方向
        for (int[] d : direct) {
            int x = i + d[0], y = j + d[1];
            // 检查是否越界
            if (x < 0 || x >= n || y < 0 || y >= m) continue;
            // 如果是陆地且未访问过
            if (graph[x][y] == 1 && !visited[x][y]) {
                visited[x][y] = true;
                curArea++;
                bfs(graph, x, y); // 继续递归遍历
            }
        }
    }

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        n = scan.nextInt();
        m = scan.nextInt();
        int[][] graph = new int[n][m];
        visited = new boolean[n][m];

        // 输入矩阵
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                graph[i][j] = scan.nextInt();
            }
        }

        // 遍历所有单元格
        for (int x = 0; x < n; ++x) {
            for (int y = 0; y < m; ++y) {
                // 如果是陆地且未访问过
                if (graph[x][y] == 1 && !visited[x][y]) {
                    visited[x][y] = true;
                    curArea = 1;
                    yes = 0;
                    bfs(graph, x, y);
                    // 如果yes没有被设置为1,说明这是一个孤岛
                    if (yes == 0) {
                        totalArea += curArea;
                    }
                }
            }
        }
        System.out.println(totalArea);
    }
}

还是用dmsxl的 方法,非孤岛全部变0了在统计。没用visited,访问了的都是0 了,count不是单独++,最后一个循环访问孤岛之前重置0.

卡玛 102.沉没孤岛

非孤岛先全部变2,不能变0不然不和海水一样了吗。

非孤岛和孤岛就区分出来了,然后二重循环改一下。

卡玛103.水流问题

简单想法,想每个节点都用一次深搜,时间复杂度到了四次方。

怎么优化呢

反过来就好啦,m x n个节点到2m+2n个节点,起点变成数量少的那批,复杂度降为三次方。这是不用visited数组的情况。

如果用visited,每个节点只会visited一次,总共只会用到一个二次方,so复杂度降到二次方。

我这样写,递归结束不了,溢出:

import java.util.*;
 class Main {
    class pair {
        int first, second;
        public pair(int first, int second) {
            this.first = first;
            this.second = second;
        }
    }

    static int count;
    static int[][] direct = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
    static List<int[]> result = new ArrayList<>();
    static int m, n,prex,prey;
    static boolean[][] visited;//需要visited,resut不能重复加

    public static void dfs(int[][] graph, int i, int j) {
        if (graph[prex][prey] > graph[i][j]) return;
        
        for (int[] d : direct) {
            int x = i + d[0], y = j + d[1];
            if (x < 0 || x >= n || y < 0 || y >= m) continue;
            if (graph[x][y] < graph[i][j]) contnue://反方向停下,需要一直变大
            if(!visited[x][y])
            {
                result.add(new int[]{x, y});
                visited[x][y] = true;
            }
            prex=x;
            prey=y;
            dfs(graph, x, y);
        }
    }

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        n = scan.nextInt();
        m = scan.nextInt();
        visited = new boolean[n][m];
        int[][] graph = new int[n][m];
        for (int i = 0; i < n; ++i) {

            for (int j = 0; j < m; ++j) {
                graph[i][j] = scan.nextInt();
            }
        }

        for (int i = 0; i < n; ++i) {
            dfs(graph, i, 0);
            dfs(graph, i, m - 1);
        }

        for (int j = 0; j < m; ++j) {
            dfs(graph, 0, j);
            dfs(graph, n - 1, j);
        }
        System.out.println(result.isEmpty());
        for(int[] r:result)
        {
            System.out.println(r[0]+" "+r[1]);
            
        }
    }
}

题目都没看清楚,是:可以达到第一组边界第二组边界的。

所以2个boolean标记数组,输出要求两个同时可以到达才加入数组。还有点细节

但是递归出口还是不很清楚

import java.util.*;

class Main {
    static int[][] direct = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
    static List<int[]> result = new ArrayList<>();
    static int m, n;
    static boolean[][] canReachOne;
    static boolean[][] canReachSecond;
    private static void bfs(int[][] graph,int i,int j,boolean[][] canReach){
        //能到边界
        canReach[i][j]=true;
        for(int[] d:direct){
            int x=i+d[0],y=j+d[1];
            if (x < 0 || x >= n || y < 0 || y >= m) continue;
            if(canReach[x][y])continue;//递归出口
            if(graph[i][j] > graph[x][y])continue;//只能往高走
            bfs(graph,x,y,canReach);
        }
    }
    public static void main(String[] args)
    {
        Scanner scan=new Scanner(System.in);
        n=scan.nextInt();
        m=scan.nextInt();
        int[][] graph=new int[n][m];
        for(int i=0;i<n;++i)
        {
            for(int j=0;j<m;++j)
            {
                graph[i][j]=scan.nextInt();
            }
        }
        canReachOne=new boolean[n][m];
        canReachSecond=new boolean[n][m];
        
        //分4个写当然也对
        for(int i=0;i<n;++i)
        {
            bfs(graph,i,0,canReachOne);
            bfs(graph,i,m-1,canReachSecond);
        }
        for(int j=0;j<m;++j)
        {
            bfs(graph,0,j,canReachOne);
            bfs(graph,n-1,j,canReachSecond);
        }
        
        
        for(int i=0;i<n;++i)
        {
            for(int j=0;j<m;++j)
            {
                if(canReachOne[i][j] && canReachSecond[i][j])System.out.println(i+" "+j);
            }
        }
    }
}

看规律前面都得有visited作为递归出口,这里canReach其实包含visited含义,所以同理。

104. 建造最大岛屿

一个岛屿一个标记,相同岛屿上的每一块 都用一样的标记,并且map存储岛屿标记对应的 面积,这样最后一个循环计算所有海水块万一变成岛屿,周围合并起来的岛屿加上自己的面积,统计最大值。

注意用什么标记的。curLoc其实可以从 1开始(有visited协助判断是否遍历过),但是从2 开始就可以不用visited判断了。

注意时间是n平方。

从2开始:

import java.util.*;

public class Main {
    class pair {
        int first, second;
        public pair(int first, int second) {
            this.first = first;
            this.second = second;
        }
    }
    static int m,n;
    static int count, curLoc = 2;
    static Deque<pair> queue = new LinkedList<>();
    static int[][] direct = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
    static List<Integer[]> result = new ArrayList<>();

    static boolean[][] visited;  // Need visited, result cannot duplicate
    public static void dfs(int[][] graph, int i, int j) {
        //每次调用dfs前是不是都判断了,那么调用一次就是一块陆地,放在前面合适
        // visited[i][j] = true;
        //岛屿不用count标记,不好搞,而且后面计算周围面积也是
        //用新的变量curLoc,独一的,计算周围面积可以判断有没有重复添加
        graph[i][j] = curLoc;
        count++;
        for (int[] d : direct) {
            int x = i + d[0], y = j + d[1];
            if (x < 0 || x >= n || y < 0 || y >= m) continue;
            if (graph[x][y] ==1 ) {//&& !visited[x][y]
                dfs(graph, x, y);
            }
        }
    }

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        n = scan.nextInt();
        m = scan.nextInt();
        int[][] graph = new int[n][m];
        // visited = new boolean[n][m];
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                graph[i][j] = scan.nextInt();
            }
        }
        int maxCount = 1;
        Map<Integer, Integer> hash = new HashMap<>();
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (graph[i][j] == 1 ) {
                    count = 0;
                    dfs(graph, i, j);
                    hash.put(curLoc, count);
                    curLoc++;
                    //maxCount初始是岛屿面积最大值
                    maxCount=Math.max( maxCount,count);
                }
            }
        }

        

        for(int i=0;i<n;++i)
        {
            for(int j=0;j<m;++j)
            {
                // System.out.print(graph[i][j]+"  ");
                if(graph[i][j]==0 )
                {
                    int zhouwei=0;
                    Set<Integer> set = new HashSet<>();
                    for(int[] d :direct)
                    {
                        int x = i + d[0], y = j + d[1];
                        if (x < 0 || x >= n || y < 0 || y >= m) continue;

                        int bianhao=graph[x][y];
                        if(!set.contains(bianhao))
                        {
                            zhouwei+=hash.getOrDefault(bianhao,0);
                            set.add(bianhao);
                        }
                    } 
                    maxCount=Math.max( maxCount,zhouwei+1);
                }

            }
        }
        System.out.println(maxCount);
    }
}

从1开始:

import java.util.*;
 
public class Main {
    class pair {
        int first, second;
        public pair(int first, int second) {
            this.first = first;
            this.second = second;
        }
    }
    static int m,n;
    static int count, curLoc = 1;
    static Deque<pair> queue = new LinkedList<>();
    static int[][] direct = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
    static List<Integer[]> result = new ArrayList<>();
 
    static boolean[][] visited;  // Need visited, result cannot duplicate
    public static void dfs(int[][] graph, int i, int j) {
        visited[i][j] = true;
        graph[i][j] = curLoc;
        count++;
        for (int[] d : direct) {
            int x = i + d[0], y = j + d[1];
            if (x < 0 || x >= n || y < 0 || y >= m) continue;
            if (graph[x][y] ==1 && !visited[x][y]) {
                dfs(graph, x, y);
            }
        }
    }
 
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        n = scan.nextInt();
        m = scan.nextInt();
        int[][] graph = new int[n][m];
        visited = new boolean[n][m];
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                graph[i][j] = scan.nextInt();
            }
        }
        int maxCount = 1;
        Map<Integer, Integer> hash = new HashMap<>();
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (graph[i][j] == 1 && !visited[i][j]) {
                    count = 0;
                    dfs(graph, i, j);
                    hash.put(curLoc, count);
                    curLoc++;
                    maxCount=Math.max( maxCount,count);
 
                }
            }
        }
 
         
 
        for(int i=0;i<n;++i)
        {
            for(int j=0;j<m;++j)
            {
                // System.out.print(graph[i][j]+"  ");
                if(graph[i][j]==0 )
                {
                    int zhouwei=0;
                    Set<Integer> set = new HashSet<>();
                    for(int[] d :direct)
                    {
                        int x = i + d[0], y = j + d[1];
                        if (x < 0 || x >= n || y < 0 || y >= m) continue;
 
                        int bianhao=graph[x][y];
                        if(!set.contains(bianhao))
                        {
                            zhouwei+=hash.getOrDefault(bianhao,0);
                            set.add(bianhao);
                        }
                    } 
                    maxCount=Math.max( maxCount,zhouwei+1);
                }
 
            }
        }
        System.out.println(maxCount);
    }
}

注意把visited放在递归函数一开始,liao pi些

110. 字符串接龙

自己写的,先生成了graph数组,再遍历,结果是遍历了A到B每一圈的节点都统计,就是二叉树要求路径长结果你求节点数。

aab7583924f643c1ba70c08a07882071.png


import java.util.*;

class Main
{
    static Deque<Integer> queue = new LinkedList<>();

    static int count=0;

    static boolean[] visited;

    static String[] strs;

    public static int bfs(int[][] graph, int start, int end) {
        queue.addLast(start);
        visited[start] = true;
        int step = 1; // 记录步数

        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                int cur = queue.pollFirst();
                if (cur == end) return step; // 找到 endStr,返回步数
                for (int j = 0; j < strs.length; ++j) {
                    if (graph[cur][j] == 0) continue;
                    if (visited[j]) continue;
                    queue.addLast(j);
                    visited[j] = true;
                }
            }
            step++; // 每一层加1步
        }

        return 0; // 未找到路径,返回0
    }

    private static boolean isDiffOne(int a, int b) {
        int diffCount = 0;
        String s1 = strs[a], s2 = strs[b];
        if (s1.length() != s2.length()) return false;
        for (int i = 0; i < s1.length(); i++) {
            if (s1.charAt(i) != s2.charAt(i)) diffCount++;
        }
        return (diffCount == 1);
    }

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt() + 2;
        int[][] graph = new int[n][n];
        strs = new String[n];

        for (int i = 0; i < n; i++) {
            strs[i] = new String(scan.next());
        }

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (isDiffOne(i, j)) graph[i][j] = 1;
                // System.out.print(graph[i][j]);
            }
            // System.out.println();
        }

        visited = new boolean[n];
        
        System.out.println(bfs(graph, 0,1));
    }
}

之后搞清楚求节点数,还是求层数。本来层序遍历那里应该练的很熟练的了。

还有区分递归 和迭代,不要迭代写一个递归出口在前面,或者循环里面写一个递归调用。

时间复杂度n2

小总结:递归or 迭代?海水陆地(周围节点上下左右direct数组)or行列代表节点一样(对称矩阵还是邻接表,遍历每个节点的邻接点)?每圈节点数量or路径长度?要不要visited数组?visited的赋值在哪里,统计or result最后的确定写哪里

13.有向图的完全可达性

把条件写在递归函数开头是处理当前节点,写在循环里面是处理下一个节点。。写在开头,对所有节点都有效,写在循环……

到现在一般递归函数参数要有 : graph邻接矩阵或者邻接表,visited数组,当前的节点i。这些以及其他可以放类属性也可以参数。

这道题也没有什么别的,就从已知的起点开始BFS或者DFS,如果一轮下来,还有节点没访问到就是+1。

邻接矩阵,邻接表

X

BFS,DFS

组合4种都要会写。

注意题目节点从1开始,加入的时候存入的时候-1,就统一了,visited数组。

import java.util.*;


public class Main {

    public static void dfs(List<Integer>[] graph, int i, boolean[] visited) {
        if(visited[i]) return;
        visited[i] = true;
        for(int j : graph[i]) {
            dfs(graph, j, visited);
        }
    }

    public static void bfs(List<Integer>[] graph, int i, boolean[] visited) {
        Deque<Integer> q = new LinkedList<>();
        q.add(i);
        while(!q.isEmpty()) {
            int j = q.pollFirst();
            visited[j] = true;
            for(int k : graph[j]) {
                if(visited[k]) continue;
                q.addLast(k);
            }
        }
    }

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int m = scan.nextInt();
        List<Integer>[] graph = new ArrayList[n];
        for(int i = 0; i < n; ++i) {
            graph[i] = new ArrayList<>();
        }
        for(int i = 0; i < m; ++i) {
            int start = scan.nextInt();
            int end = scan.nextInt();
            graph[start - 1].add(end - 1);
        }
        boolean[] visited = new boolean[n];
        // dfs(graph, 0, visited);
        bfs(graph, 0, visited);
        for(boolean v : visited) {
            if(!v) {
                System.out.println(-1);
                return;
            }
        }
        System.out.println(1);
    }
}

106. 岛屿的周长

不要陷入惯性思维,这个题目幽你一默。

用BFS,DFS?可以但没必要

没有算法分类,直接就是根据规律挨个统计而已。

方法一注意除了海水,还要看四条边界也是周长一部分。

方法二找到陆地之后,你不用循环k=4这样。规律是陆地✖️4 减去 所有重叠挨在一起的边。这个重叠的边可以只算一半,一个直角那样的,就行。比如左上,因为重叠的一对边一定有一边是一个左或者上。

import java.util.*;

public class Main {

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int m = scan.nextInt();
        int[][] graph=new int[n][m];
        for(int i = 0; i < n; ++i) {
            for(int j = 0; j < m; ++j) {
                graph[i][j] = scan.nextInt();
            }
        }
        int count=0;//陆地数
        int overlapBian=0;
        for(int i = 0; i < n; ++i) {
            for(int j = 0; j < m; ++j) {
                if(graph[i][j] ==1){
                    count++;
                    if(i>0 && graph[i-1][j]==1)overlapBian++;
                    if(j>0 && graph[i][j-1]==1)overlapBian++;
                }
            }
        }
        System.out.println(count * 4 - overlapBian*2);
    }
}

并查集理论

也就是说,无论使用并查集模板里哪一个函数(除了init函数),都会有路径压缩的过程。

路径压缩是为了一个集合里面只有两层,一个根节点,下面一排。

路径压缩后的并查集时间复杂度在O(logn)与O(1)之间,且随着查询或者合并操作的增加,时间复杂度会越来越趋于O(1)。

最后不能直接判断father[s]=d来看,在同一条路径下面而不是确定d是根,所以只能isSame才可以。

其实代码好写,重要是理解什么时候用这个:2个节点是不是同一个根;2个集合合并。

import java.util.*;

public class Main {
    static int[] father;

    private static void init() {
        for (int i = 0; i < father.length; ++i) {
            father[i] = i;
        }
    }

    private static int find(int x) {
        return x == father[x] ? x : (father[x] = find(father[x]));
    }

    private static boolean isSame(int u, int v) {
        u = find(u);
        v = find(v);
        return u == v;
    }

    private static void join(int u, int v) {
        u = find(u);
        v = find(v);
        if (u == v) return;
        father[u] = v;
    }

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int m = scan.nextInt();
        father = new int[n];
        init();
        for (int i = 0; i < m; ++i) {
            int a = scan.nextInt()-1;
            int b = scan.nextInt()-1;
            join(a, b);
        }
        int s = scan.nextInt()-1;
        int d = scan.nextInt()-1;
        if (isSame(s, d)) {
            System.out.println(1);
        } else {
            System.out.println(0);
        }
    }
}

108. 冗余连接

偏移的问题;

为什么一找到一个根相同的就对,因为答案就一个,只会有一次跟相同,那你re不return都一样答案。

import java.util.*;


public class Main {
    static int[] father;
    private static void init(){
        for(int i=0;i<father.length;++i){
            father[i]=i;
        }
    } 
        private static int find(int x) {
            return x== father[x]?x:(father[x] = find(father[x]));
        }
        private static boolean isSame(int u, int v ){
            u=find(u);
            v=find(v);
            return u==v;
        }
        private static void join(int u ,int v){
            u=find(u);
            v=find(v);
            if(u==v){return;}
            father[u]=v;  
        }
        public static void main(String[]  args){
            Scanner scan = new Scanner (System.in);
            int n=scan.nextInt();
            //有偏移不用-1,father多增一个。because有的例子输入了0
            father = new int[n+1];
            init();
            for(int i=0 ;i<n; ++i){
                int a=scan.nextInt();
                int b=scan.nextInt();
                if(isSame(a,b)){
                    System.out.println((a)+" "+(b));
                    return ;}
                else{ 
                    join(a,b);}
            }
        }
        
        
}

109. 冗余连接II

一个环就是去掉方向看作无向图就有一个环。因为是n个点n条有向边。

直接看答案了,把三种情况都说了。

1加一条倒着的(不指向根):(2度点两条入边只可能去一个)

08240c2e89a640c6b48981392287d786.png

2加一条倒着的(指向根):(得遍历判断了:顺着找发现一条边两点同根就是答案)

b35cf9e015be4c2184e585d4e37735b3.png

3加一条顺着的:(2度点两条入边都可去)

88a6d554320c45a0af314826c6fb860d.png

也可以不线性哈,这里随便画的。去掉之后是有向树就是对的。

并查集体现在,判断某条边能不能join。

所以1、3一起看,如果没找到,接着判断2.

import java.util.*;

class Main{
    static int[] father;
    
    static void init(){
        for(int i=0;i<father.length;++i){
            father[i]=i;
        }
    }
    
    static int find(int u){
        return u==father[u]?u:(father[u]=find(father[u]));
    }
    
    static boolean isSame(int u,int v){
        u=find(u);
        v=find(v);
        return u==v;
    }
    
    //u->v
    static boolean join(int u,int v){
        u=find(u);
        v=find(v);
        if(u==v)return false;
        father[v]=u;
        return true;
    }
    //找到某条边两端点同根
    static void canDeleteShu(int[][] graph){
        init();
        for(int i=0;i<graph.length;++i){
            int a=graph[i][0],b=graph[i][1];
            if(isSame(a,b)){
                System.out.println(a+" "+b);
                return;
            }
            join(a,b);
        }
    }
    
    //如果删了还有环return false
    static boolean canDeleteHuan(int[][] graph,int index){
        init();
        for(int i=0;i<graph.length;++i){
            int a=graph[i][0],b=graph[i][1];
            if(index==i)continue;
            if(!join(a,b))return false;
        }
        return true;
    }
    
    public static void main(String[] args){
        Scanner scan=new Scanner(System.in);
        int n = scan.nextInt();
        father = new int[n+1] ;
        int[][] graph=new int[n][2];
        int[] du=new int[n+1];//存储入度
        for(int i=0;i<n;++i){
            graph[i][0]=scan.nextInt();
            graph[i][1]=scan.nextInt();
            du[graph[i][1]]++;
        }
        //有入度为2 情况
        //倒着判断,canDeleteHuan判断能不能删
        for(int i=n-1;i>=0;--i){
            if(du[graph[i][1]]==2){
                if(canDeleteHuan(graph,i)){
                    System.out.println(graph[i][0]+" "+graph[i][1]);
                    return;
                }
                //如果删了还有环,这条边不能删除,接着找后面那条
                else continue;
                
            }
        }
        //根参与环的情况
        canDeleteShu(graph);
    }
}

两个函数就是用isSame()和join()判断有没有环,所以并查集判断有无环很方便。我是说不看方向的无向图,或者有向环不看箭头的环。

最小生成树

prim 53. 寻宝(第七期模拟笔试)

复习一下Prim,选择一个节点加入到生成树集合,每次计算u(生成树集合)到v(非生成树节点集合)的距离(更新minDist 数组),同样把最短距离的v中的点加入到u……

minDist 数组 就记录每个节点到生成树集合 的距离。

怎么判断 生成树节点 和 非生成树节点?bool数组

注意下面的主语

83bedb8230c44f579095c352da378f3a.png

8ff9acc65ff64d0e9f0b66acc1933973.png

mniDist,定义记清楚,当某个点选中为生成树节点,mniDist那个值也就不变了,就是最后结果。

n次的:

adfdc933233545a2a704ab6a9f11a7ae.png

注意双向边,graph[x][y]和graph[y][x];

注意 每次更新只用关注所有点到cur。

注意 i=1 始终10001,统计只用关注i=1以后的。

再关注记录 这棵最小生成树的所有组成边,不用二维数组记录(parent一维),在更新mniDisc里面更新parent,因为最后的结果一定是最小的边,被选上了(true)就不会再更新了。最后所有节点都被选中,parent就是最终最小生成树的所有组成边。

graph也一定要初始化,没有边的默认最大值。

import java.util.*;

public class Main{
    public static void main(String[] args){
        Scanner scan=new Scanner(System.in);
        int v=scan.nextInt();
        int e=scan.nextInt();
        int[] minDisc=new int[v+1];//节点有偏移
        Arrays.fill(minDisc, 10001);//一开始都是非生成树节点
        int[][] graph=new int[v+1][v+1];
        for(int i=0;i<=v;++i){
            Arrays.fill(graph[i], 10001);//一定要初始化
        }
        for(int i=0;i<e;++i){
            int x=scan.nextInt();
            int y=scan.nextInt();
            int val=scan.nextInt();
            //无向图,要对称
            graph[x][y]=val;
            graph[y][x]=val;
        }
        boolean[] isInTree=new boolean[v+1];
        int[] parent=new int[v+1];//顶点j对应的最小生成树边的另一个顶点parent[j]
        for(int i=1;i<=v;++i){
            int cur=1;
            int minDiscMin=10001;//minDisc里面最小的《非生成树节点》
            for(int j=1;j<=v;++j){
                if(minDisc[j]<minDiscMin && !isInTree[j]){
                    minDiscMin=minDisc[j];
                    cur=j;
                }
            }
            //得到cur,选中,更新isInTree
            isInTree[cur]=true;
            //更新minDisc数组,只用更新与cur有关的
            for(int k=1;k<=v;++k){
                //重新统计 非生成树节点k 到 生成树节点的最短距离,仅仅与变动的cur比较,更新
                // 由于cur节点是新加入到最小生成树,那么只需要关心与 cur 相连的 非生成树节点 的距离 是否比 原来 非生成树节点到生成树节点的距离更小了呢
                if(graph[k][cur]<minDisc[k] && !isInTree[k]){
                    minDisc[k]=graph[k][cur];//更新
                    parent[k]=cur;
                }
            }

        }
        int count=0;
        for(int i=2;i<=v;++i){//i=1没有更新过,一开始就是生成树节点;统计其他的v-1条边即可
            count+=minDisc[i];
        }
        System.out.println(count);
        
        for(int i=2;i<=v;++i){
            System.out.println(i+" "+parent[i]);
        }

        

    }
}

parent和mniDist都是在!isInTree[]条件下。

 克鲁斯卡尔 53. 寻宝(第七期模拟笔试)

a340d8da212c48d7a521442ba213876b.png

上一题想用 并查集,其实是这里能用到。

主要就是选V-1条边,条件是不能在同一个集合里面(!isSame()),就可以join()。

记录边的话,可以用之前的parent,注意每次选边的过程,按照并查集是v1->v2,所以v2是新选的每次都唯一,so只能v2作为下标。当然也可以直接一个集合挨个add v1和v2(Prim只能parent记录,因为循环结束才能知道结果):Kruskal 算法 输出边的话,相对prim 要容易很多,因为 Kruskal 本来就是直接操作边,边的结构自然清晰,不用像 prim一样 需要再将节点连成线输出边 (因为prim是对节点操作,而 Kruskal是对边操作,这是本质区别)

import java.util.*;

public class Main{
    static int[] father;
    
    static void init(){
        for(int i=0;i<father.length;++i){
            father[i]=i;
        }
    }
    
    static int find(int x){
        return x==father[x]?x:(father[x]= find(father[x]));
    }
    
    static boolean isSame(int u,int v){
         u=find(u);
         v=find(v);
        return u==v;
        
    }
    
    // u->v
    static void join(int u, int v){
          u=find(u);
         v=find(v);
        if(u==v)return;
        father[u]=v;
    }
    
    public static void main(String[] args){
        Scanner scan=new Scanner(System.in);
        int v=scan.nextInt();
        int e=scan.nextInt();
        
         
        List<int[]> graph=new ArrayList<>();
        
        for(int i=0;i<e;++i)
        {
            int v1=scan.nextInt();
            int v2=scan.nextInt();
            int val=scan.nextInt();
            graph.add(new int[]{v1,v2,val});
        }
        
        //排序,lambda,2维数组的元素a->按照a的哪个元素ai比较排序,默认从小到大
        graph.sort(Comparator.comparingInt(arr -> arr[2]));


        int count=0,minDist=0;
        //选v-1条边
        father =new int[v+1];
        init();//记得初始化
        //parent记录选的边,father不行因为并查集压缩路径了
        int[] parent=new int[v+1];
        for(int[] g:graph){
            int v1=g[0],v2=g[1],val=g[2];
            if(isSame(v1,v2))continue;
            // System.out.println(v1+" "+v2);
            join(v1,v2);
            parent[v2]=v1;//hhh这个顺序有讲究
            //v2每一次都是新选的节点,所以不会被覆盖
            count++;
            
            minDist+=val;
            if(count==v-1)break;//选了v-1条就结束
        }
        
        for(int i=1;i<=v;++i){
            System.out.println(i+" "+parent[i]);
        }
        
        System.out.println(minDist);

    }
}

比较这两个:

f171a9795aa54099be93d0968956a7b1.png

二刷忘记排序,复习的时候回想一下总体过程,带点脑子。

117. 软件构建

3b418dc6ce19441fa3870ce45f2c023c.png

思路:1、选入度为0 的点;2、在图中删除这个点。

所以,要统计入度,删除之后,点指向的其他点的入度要改变,怎么找到其他点?直接一个下标->List ,List数组 来存上 每个点 指向的 其他点。

q是一定要的,步骤2 看q 来调整入度 和 加入点 到q。

import java.util.*;

public class Main{
    public static void main(String[] args){
        Scanner scan=new Scanner(System.in);
        int n=scan.nextInt();
        int m=scan.nextInt();

        int[] inDegree=new int[n];
        List<Integer>[] outedge=new List[n];
        for(int i=0;i<n;i++){
            outedge[i]=new ArrayList<>();
        }
        //统计 入度、出边顶点
        for(int i=0;i<m;++i){
            int a=scan.nextInt();
            int b=scan.nextInt();
            inDegree[b]++;
            outedge[a].add(b);
        }
        //q是必须的,记录步骤1 直接 和 间接 删除的点,好在步2 调整相关点的入度
        Queue<Integer> q=new LinkedList<>();
        List<Integer> result=new ArrayList<>();
        //开始步骤1、2
        //一开始入度就是0的入队
        for(int i=0;i<n;++i){
            if(inDegree[i]==0)q.add(i);
        }
        
        //步骤2
        while(!q.isEmpty()){
            int a=q.poll();
            result.add(a);
            for(int j :outedge[a]){  
                //如果相关节点受影响之后(是前++)变成了无入度,又要加入到q
                if(--inDegree[j]==0){
                    q.add(j);
                }
            }
        }
        
        
        // 还要判断环
        if(result.size()<n){
            System.out.println(-1);
            return;
        }
        for(int i=0;i<result.size();++i ){
            System.out.print(result.get(i));
            if(i<result.size()-1)System.out.print(" ");
        }
    }
}

最短路径

迪杰斯特拉

dijkstra算法:在有权图(权值非负数)中求从起点到其他节点的最短路径算法。

为什么 和 Prim这么像,过程都是一个数组——mniDist的更新(n-1)轮:每轮确定一个点(距离最短的),然后更新此数组。最后确定完除了起点之外的 n-1个点:

import java.util.*;

class Main{
    public static void main(String[] args)
    {
        Scanner scan=new Scanner (System.in);
        int n=scan.nextInt();
        int m=scan.nextInt();
        int[][] graph=new int[n+1][n+1];
        for(int i=1;i<=n;++i)//又忘记初始化
        {
            Arrays.fill(graph[i],Integer.MAX_VALUE);
        }
        for(int i=0;i<m;++i){
            int v1=scan.nextInt();
            int v2=scan.nextInt();
            graph[v1][v2]=scan.nextInt();
            // graph[v2][v1]=scan.nextInt();
        }
        //源头到没选中节点的最短距离
        int[] minDist=new int[n+1];
        Arrays.fill(minDist,Integer.MAX_VALUE);
        minDist[1]=0;//得初始化:起点到自己距离是0
        boolean[] visited=new boolean[n+1];
        for(int j=1;j<n;++j){
            //不能是别的负数,最后一轮
            int cur=1;
            int minD=Integer.MAX_VALUE;
            for(int i=1;i<=n;++i){
                if(minDist[i] <minD&&!visited[i]){
                    minD=minDist[i];
                    cur=i;
                }
            }
            visited[cur]=true;
            for(int i=1;i<=n;++i){
                //要不然出现负数,有溢出
                if(minDist[cur]==Integer.MAX_VALUE || graph[cur][i]==Integer.MAX_VALUE)continue;
                
                if(minDist[cur]+graph[cur][i]<minDist[i]&&!visited[i]){
                    minDist[i]=minDist[cur]+graph[cur][i];
                }
            }
            // for(int i=2;i<=n;++i){
            //     System.out.print(minDist[i]+"  ");
                
            // }
            // System.out.println();
        }
        if(minDist[n]==Integer.MAX_VALUE)System.out.println(-1);
        else System.out.println(minDist[n]);
    }
}

这是与prim无异的朴素版算法,也是考研学的那种过程的思想。

注意graph、起点的初始化,mniDist里面某个点一旦被选,不再参与比较,也不再更新距离。

有个问题,上面总的for循环,应该是≤n轮,为什么<n轮也行。

47. 参加科学大会(第六期模拟笔试)

前提:稀疏图,所以尽量让 时复 跟节点没关系。

改进:

1、堆优化,就是空间换时间,因为mniDist数组找最小值时间有点长与n有关,所以用PriorityQueue存下mniDist下标-权值 对帮忙找最小值,PriorityQueue会把所有的边放一遍出一遍,那么就只跟边有关。是的就这个作用而已。

2、另外结合邻接表,能够让总的 时间复杂度 更低。如果还是用邻接矩阵的话,里面更新数组 的时间复 还是 和 n有关,不行。

这样,大循环是E,小循环1是logE,小循环2是决定了大循环,其实可以看做1。可以细想。

import java.util.*;

class Main{

    public static void main(String[] args)
    {
        class edge{
            int v2;
            int val;

            public edge(int b, int c) {
                v2 = b;
                val = c;
            }
        }
        
        class pair{
            int index;
            int val;
            public pair(int a, int b) {
                index = a;
                val = b;
            }
        }
        
        Scanner scan=new Scanner(System.in);
        int n=scan.nextInt();
        int m=scan.nextInt();

        int[] mniDist = new int[n+1];
        Arrays.fill(mniDist,Integer.MAX_VALUE);
        mniDist[1]=0;
        //辅助mniDist——意义相同,pair是下标-权值 对
        PriorityQueue<pair> priQueue=new PriorityQueue<>(new Comparator<pair>(){
            public int compare(pair e1,pair e2)
            {
                return e1.val-e2.val;
            }
        });
        priQueue.add(new pair(1,0));
        //适合稀疏图的——邻接表来表示
        List<edge>[] graph=new List[n+1];
        for(int i=0;i<=n;i++){
            graph[i]=new ArrayList<>();
        }

        for(int i=0;i<m;++i){
            int a=scan.nextInt();
            int b=scan.nextInt();
            int c=scan.nextInt();
            graph[a].add(new edge(b,c));
            
        }
        boolean[] visited=new boolean[n+1];
        while(!priQueue.isEmpty()){
            pair cur=priQueue.poll();
            visited[cur.index]=true;//选中
            for(edge e:graph[cur.index]){
                int j=e.v2;
                if(!visited[j]&&cur.val + e.val<mniDist[j]){
                    mniDist[j]=cur.val + e.val;
                    priQueue.add(new pair(j,mniDist[j]));
                }
            }
        }
        if(mniDist[n]==Integer.MAX_VALUE)System.out.println(-1);
        else System.out.println(mniDist[n]);
    }
}
  • 时间复杂度:O(ElogE) E 为边的数量。堆找最小值 是logE。
  • 空间复杂度:O(N + E) N 为节点的数量。堆是E,mniDist是N。

注意堆怎么实现的?邻接表的实现。

94. 城市间货物运输 I

在求单源最短路的方法中,使用dijkstra 的话,则要求图中边的权值都为正数。

b26aae4654994cb3bc31d6f02b9e0771.png

3b4556941c734a3e904e67f617302c94.png

import  java.util.*;

class Main{
    public static void main(String[] args){
        class edge{
            int s,t,v;
            public edge(int a,int b, int c){
                s=a;
                t=b;
                v=c;
            }
        }
        
        Scanner scan=new Scanner(System.in);
        int n=scan.nextInt();
        int m=scan.nextInt();
        int[] mniDist=new int[n+1];
        Arrays.fill(mniDist,Integer.MAX_VALUE);
        mniDist[1]=0;//还是单源的哈
        
        List<edge> Edges=new ArrayList<>();
        
        for(int i=0;i<m;++i){
            int a=scan.nextInt();
            int b=scan.nextInt();
            int c=scan.nextInt();
            Edges.add(new edge(a,b,c));
        }
        int[] copyMniDist=new int[n+1];
        //松弛n-1不是m-1次,没有环的
        for(int i=1;i<n;++i)
        {
            copyMniDist = Arrays.copyOf(mniDist,mniDist.length);
            for(edge e:Edges){
                if(copyMniDist[e.s]==Integer.MAX_VALUE)continue;
                //最小运输成本
                mniDist[e.t]=Math.min(mniDist[e.t],copyMniDist[e.s]+e.v);
            }
        }
        // for(int i=1;i<=n;++i)
        // {
        //     System.out.println(mniDist[i]);
        // }
        if(mniDist[n]==Integer.MAX_VALUE)System.out.println("unconnected");
        else System.out.println(mniDist[n]);
    }
}

像简单的 动态规划。

时间复杂度: O(N * E)

没有负权回路,remember。

每i轮确定了和起点有i条边的点的最短距离。没有回路最多n-1条边,所以n-1轮。

注意和迪杰斯特拉 的区别。

代码随想录

大家可以发现 Bellman_ford 算法每次松弛 都是对所有边进行松弛。

但真正有效的松弛,是基于已经计算过的节点在做的松弛。

照着层次遍历 来做,不加别的,因为图圈圈又绕绕,队列里面会无限制的加入点噢!

所以得加入visited数组,就是队列里面已经有的,不用重复添加了,要不然溢出。进队true出队false。

下面的程序试过了,除了负权回路,全正回路也不行。

import java.util.*;

class Main{
    public static void main(String[] args){
        class edge{
            int t,v;
            public edge(int b,int c){
                t=b;
                v=c;
            }
        }
        Scanner scan=new Scanner (System.in);
        int n=scan.nextInt();
        int m=scan.nextInt();
        
        //邻接表更高效
        List<edge>[] graph=new ArrayList[n+1];
        for(int i=0;i<=n;++i)
        {
            graph[i] = new ArrayList<>();
        }
        
        for(int i=0;i<m;++i)
        {
            int a=scan.nextInt();
            int b=scan.nextInt();
            int c=scan.nextInt();
            graph[a].add(new edge(b,c));
        }
        
        int[] mniDist=new int[n+1];
        Arrays.fill(mniDist,Integer.MAX_VALUE);
        mniDist[1]=0;
        
        boolean[] visited=new boolean[n+1];//只是用来保证不重复添加的
        
        Deque<Integer> alreadyDots=new LinkedList<>();
        alreadyDots.add(1);
        visited[1]=true;
        
        while(!alreadyDots.isEmpty()){
            int cur=alreadyDots.pollFirst();
            visited[cur]=false;//只有在队列的元素才是true
            for(edge e:graph[cur]){
                //更新是一定要更新的,入不入对是不一定的
                mniDist[e.t]=Math.min(mniDist[cur]+e.v,mniDist[e.t]);
                if(visited[e.t]==false)
                {
                    alreadyDots.addLast(e.t);
                    visited[e.t]=true;
                    }
            }
        }
        
        // for(int i=1;i<n;++i){
        //     for(edge e:graph){
        //         if(mniDist[e.s]==Integer.MAX_VALUE)continue;
        //         mniDist[e.t]=Math.min(mniDist[e.s]+e.v,mniDist[e.t]);
                
        //     }
        // }
        
        // for(int i=1;i<n;++i){
        //     System.out.println(mniDist[i]); 
              
        // }
        
        if(mniDist[n]==Integer.MAX_VALUE)System.out.println("unconnected");
        else System.out.println(mniDist[n]);
    }
}

发现问题所在,只有更新了mniDist的,才能进队列,没有更新的本来是目前最小值,没有变化也不会影响他连着的一堆点,所以不管

import java.util.*;

class Main{
    public static void main(String[] args){
        class edge{
            int t,v;
            public edge(int b,int c){
                t=b;
                v=c;
            }
        }
        Scanner scan=new Scanner (System.in);
        int n=scan.nextInt();
        int m=scan.nextInt();
        
        //链表更高效
        List<edge>[] graph=new ArrayList[n+1];
        for(int i=0;i<=n;++i)
        {
            graph[i] = new ArrayList<>();
        }
        
        for(int i=0;i<m;++i)
        {
            int a=scan.nextInt();
            int b=scan.nextInt();
            int c=scan.nextInt();
            graph[a].add(new edge(b,c));
        }
        
        int[] mniDist=new int[n+1];
        Arrays.fill(mniDist,Integer.MAX_VALUE);
        mniDist[1]=0;
        
        boolean[] visited=new boolean[n+1];//只是用来保证不重复添加的
        
        //放更新了的点
        Deque<Integer> alreadyDots=new LinkedList<>();
        alreadyDots.add(1);
        // visited[1]=true;
        
        while(!alreadyDots.isEmpty()){
            int cur=alreadyDots.pollFirst();
            visited[cur]=false;//只有在队列的元素才是true
            for(edge e:graph[cur]){
                //更新是一定要更新的,入不入对是不一定的
                //更新是入队的前提
                if(mniDist[e.t]>mniDist[cur]+e.v){
                    mniDist[e.t]=mniDist[cur]+e.v;
                    //不在队列但已经是最优,不会管,可以有全+回路
                    if(visited[e.t]==false)
                    {
                        alreadyDots.addLast(e.t);
                        visited[e.t]=true;
                    }
                }
                
            }
        }
        
        // for(int i=1;i<n;++i){
        //     for(edge e:graph){
        //         if(mniDist[e.s]==Integer.MAX_VALUE)continue;
        //         mniDist[e.t]=Math.min(mniDist[e.s]+e.v,mniDist[e.t]);
                
        //     }
        // }
        
        // for(int i=1;i<n;++i){
        //     System.out.println(mniDist[i]); 
              
        // }
        
        if(mniDist[n]==Integer.MAX_VALUE)System.out.println("unconnected");
        else System.out.println(mniDist[n]);
    }
}

正确的算法以上。但是仍然是不适应有负权回路,因为全正,比如1-2,2-3,3-1,判断3-1,if条件肯定不满意,3到1 权值加上3的,定必1本身的大。但是负权回路,也就是图中出现环且环上的边总权值为负数   的话,会无线循环下去的。

越稠密图,时间越大,所以不一定总比bellman_ford优秀。总的是O(k*n),完全图那种会退化到O(E*n).

95. 城市间货物运输 II

没有想的那么高深,

1、朴素版第n次mniDist还有更新,那代表会一直更新下去,circle。

2、优化版有某个节点入队n次以上,代表mniDist必更新了n次以上,circle。

import java.util.*;

class Main{
    public static void main(String[] args){
        class edge{
            int t,v;
            public edge(int a,int b){
                t=a;
                v=b;
            }
        }
        Scanner scan=new Scanner(System.in);
        int n=scan.nextInt();
        int m=scan.nextInt();
        List<edge>[] graph=new List[n+1];
        for(int i=0;i<=n;++i){
            graph[i]=new ArrayList<>();
        }
        for(int i=0;i<m;++i){
            int a=scan.nextInt();
            int b=scan.nextInt();
            int c=scan.nextInt();
            graph[a].add(new edge(b,c));
        }
        int[] mniDist=new int[n+1];
        Arrays.fill(mniDist,Integer.MAX_VALUE);
        mniDist[1]=0;
        
        Deque<Integer> q=new LinkedList<>();
        q.add(1);
        
        boolean[] isInQueue=new boolean[n+1];
        isInQueue[1]=true;
        
        int count=0;
        
        int[] inCount=new int[n+1];
        Arrays.fill(inCount,0);
        inCount[1]=1;
        
        
        
        while(!q.isEmpty()){
            int cur=q.pollFirst();
            isInQueue[cur]=false;
            
            for(edge e:graph[cur]){
                if(mniDist[cur]+e.v<mniDist[e.t]){
                    mniDist[e.t]=mniDist[cur]+e.v;
                    if(isInQueue[e.t]==false){
                        q.add(e.t);
                        isInQueue[e.t]=true;
                        inCount[e.t]++;
                        if(inCount[e.t]>=n){
                            System.out.println("circle");
                            return;
                        }
                    }
                }
            }
        }
        if(mniDist[n]==Integer.MAX_VALUE)System.out.println("unconnected");
        else System.out.println(mniDist[n]);
        // System.out.println(count);
        
    }
}

感觉会SPFA的就行了,bellman_ford的,是第N次条件还满足(即要更新mniDist数组)就是圈。

96. 城市间货物运输 III

可以发现 同样的图,边的顺序不一样,使用版本一的代码 每次松弛更新的节点也是不一样的。

而边的顺序是随机的,是题目给我们的,所以本题我们才需要 记录上一次松弛的minDist,来保障 每一次对所有边松弛的结果。

也就是,之前用版本一,是没有负权回路的所适用的,要是有的话,(1,n-1)过程中的结果是根据边的顺序来的,不确定,只能确定第n-1次就是最后一次是固定的答案:

import java.util.*;

class Main{
    public static void main(String[] args){
        class pair{
            int t,v;
            public pair(int a,int b){
                t=a;
                v=b;
            }
        } 
        class edge{
            int s,t,v;
            public edge(int c,int a,int b){
                s=c;
                t=a;
                v=b;
            }
        }
        Scanner scan=new Scanner(System.in);
        int n=scan.nextInt();
        int m=scan.nextInt();
        
        int[] mniDist=new int[n+1];
        Arrays.fill(mniDist,Integer.MAX_VALUE);
        
        
        List<edge> graph = new LinkedList<>();
        
        for(int i=0;i<m;++i){
            int a=scan.nextInt();
            int b=scan.nextInt();
            int c=scan.nextInt();
            graph.add(new edge(a,b,c));
            
        }
        int src=scan.nextInt();
        int dst=scan.nextInt();
        int k=scan.nextInt();
        
        
        mniDist[src]=0;
        
        int[] mniDistCopy=new int[n+1];
        
        for(int i=1;i<=k+1;++i){
            //不能直接=
            mniDistCopy = Arrays.copyOf(mniDist, mniDist.length);

            for(edge e:graph){
                if(mniDistCopy[e.s]<Integer.MAX_VALUE &&mniDistCopy[e.s]+e.v<mniDist[e.t]){
                    //根据上一轮的来
                    mniDist[e.t]=mniDistCopy[e.s]+e.v;
                }
            }
        }
        
        if(mniDist[dst]==Integer.MAX_VALUE)System.out.println("unreachable");
        else System.out.println(mniDist[dst]);
        
    }
}

SPFA的话,visited要放到循环外面才对,而且不使用copy数组,会超时

import java.util.*;

class Main{
    public static void main(String[] args){
        class edge{
            int t,v;
            public edge(int b,int c){
                t=b;
                v=c;
            }
        }
        Scanner scan=new Scanner (System.in);
        int n=scan.nextInt();
        int m=scan.nextInt();
        
        //链表更高效
        List<edge>[] graph=new ArrayList[n+1];
        for(int i=0;i<=n;++i)
        {
            graph[i] = new ArrayList<>();
        }
        
        for(int i=0;i<m;++i)
        {
            int a=scan.nextInt();
            int b=scan.nextInt();
            int c=scan.nextInt();
            graph[a].add(new edge(b,c));
        }
        
        int src=scan.nextInt();
        int dst=scan.nextInt();
        int k=scan.nextInt();
        
        int[] mniDist=new int[n+1];
        Arrays.fill(mniDist,Integer.MAX_VALUE);
        mniDist[src]=0;
        
        //只是用来保证不重复添加的
        
        //放更新了的点
        Deque<Integer> alreadyDots=new LinkedList<>();
        alreadyDots.add(src);
        
        
        int[] mniDistCopy=new int[n+1];
        
        while(k-->=0 && !alreadyDots.isEmpty()){
            int qSize=alreadyDots.size();
            mniDistCopy=Arrays.copyOf(mniDist,mniDist.length);
            //visited放循环外面会错
            boolean[] visited=new boolean[n+1];
            // System.out.println(qSize);
            while(qSize-->0){
                int cur=alreadyDots.pollFirst();
                visited[cur]=false;//只有在队列的元素才是true
                for(edge e:graph[cur]){
                //更新是一定要更新的,入不入对是不一定的
                //更新是入队的前提
                    if(mniDist[e.t]>mniDistCopy[cur]+e.v){
                        mniDist[e.t]=mniDistCopy[cur]+e.v;
                        //不在队列但已经是最优,不会管,可以有全+回路
                        if(visited[e.t]==false)
                        {
                            alreadyDots.addLast(e.t);
                            visited[e.t]=true;
                        }
                    }
                }
            }
        }
        
        
        if(mniDist[dst]==Integer.MAX_VALUE)System.out.println("unreachable");
        else System.out.println(mniDist[dst]);
    }
}

以后都用这个copy数组好了,有没有负权回路都不会错。

拓展四(能否用dijkstra)

二刷看吧。

97. 小明逛公园

数据结构的过程,大循环是遍历途径节点,所以k是1到n。

原答案先用了 三维数组,然后优化成二维数组,也就是做题的做法。

import java.util.*;

class Main{
    public static void main(String[] args){
        Scanner scan=new Scanner(System.in);
        int n = scan.nextInt();
        int m = scan.nextInt();
        int[][] graph=new int[n+1][n+1];
        for(int i=0;i<=n;++i)
        {
            Arrays.fill(graph[i],Integer.MAX_VALUE);
        }
        //公园路,双向图
        for(int i=0;i<m;++i){
            int a = scan.nextInt();
            int b = scan.nextInt();
            int c = scan.nextInt();
            graph[a][b]=c;
            graph[b][a]=c;
        }
        for(int k=1;k<=n;++k)//遍历 途径1-n ,找最小值
        {
            for(int i=1;i<=n;++i){
               for(int j=1;j<=n;++j){
                   //防溢出
                   if(graph[i][k]==Integer.MAX_VALUE||graph[k][j]==Integer.MAX_VALUE)continue;
                    graph[i][j]=Math.min(graph[i][j],graph[i][k]+graph[k][j]);
                } 
            }
        }
        int q=scan.nextInt();
        for(int i=0;i<q;++i){
            int a = scan.nextInt();
            int b = scan.nextInt();
            if(graph[a][b]==Integer.MAX_VALUE){
                System.out.println(-1);
            }
            else{
                System.out.println(graph[a][b]);
            }
        }
    }
}

时间当然是O(n3);

注意细节,公园是双向图,别溢出了记得跳过。

二刷有时间看一下二维DP推导过程,说了k可不可以放里面,为什么。

127. 骑士的攻击

要么数组越界,要么超时

一开始觉得,找到唯一一个最小距离的下一个点加入就好了,但是不行。直接用堆存这些节点以及对应到终点的距离。每次周边的所有可能 还是都会存进去,但是一直优先取距离小的节点。

所以启发式体现完全靠堆,一直都是小距离的弹出,找到终点的过程是越来越小的距离点进堆,所以之前一些远距离点会慢慢下沉,没有出来的可能——那么问题来了,A * 在一次路径搜索中,大量不需要访问的节点都在队列里,会造成空间的过度消耗。IDA * 算法 对这一空间增长问题进行了优化(没事可以看看)

真的注意的点太多了

1、老是时间超限,最好用欧拉距离,可能欧式开根号时间长。也可以欧式不开根号直接用距离平方。

2、你必须得用的是visited数组,不然一直循环下去?所以加一个count还不如就用 int数组,=0就代表没有访问过。graph的意义是起点到某点的距离。!=1代表访问过了。

3、这个堆里面的类到底怎么做,坐标要有的,最终要得到f,传入3个数让构造函数自己计算f、h就好了。

4、堆的比较器,一直记不住,就(参数)->canshu1.**-canshu2.**就行了。

5、g是上一个的+5

6、弹出节点是终点的话就可以停止了,return,否则也超市

// import java.util.*;

// public class Main{
//     static int b1,b2;
//     static int[][] directs = new int[][]{
//             {-2, -1},
//             {-2, 1},
//             {-1, 2},
//             {1, 2},
//             {2, 1},
//             {2, -1},
//             {1, -2},
//             {-1, -2}
//     };
    
//     static int[][] graph=new int[1001][1001];

//     //坐标-距离 ,是堆的元素,
//     static class knight{
//         int x,y;
//         double g,h,f;//f是总的
        
//         //传入四个就可以了
//         public knight(int a,int b,double c,double d){
//             x=a;
//             y=b;
//             g=c;
//             h=d;
//             f=g+h;
//         }

//         public knight() {

//         }
//     }
//     //计算欧式距离的,也提取成函数
//     private static double ouDistance(int x,int y){
//         return Math.sqrt(Math.pow(x-b1,2)+Math.pow(y-b2,2));
//     }

//     private static void astar(knight k){
//         PriorityQueue<knight> queue=new PriorityQueue<>((k1,k2)-> (int) (k1.f-k2.f));
//         queue.add(k);
//         int count=0;
//         while(!queue.isEmpty()){
//             knight node=queue.poll();//最小的
//             for(int[] d:directs){
                
//                 if(node.x == b1 && node.y==b2)return ;
//                 int x=node.x+d[0];
//                 int y=node.y+d[1];
//                 //bfs有visited记录的
//                 if (x < 1 || y < 1 || x > 1000 || y > 1000 || graph[x][y]!=0)continue;
//                 knight next = new knight(x,y,node.g+5,ouDistance(x,y),node.g+5+ouDistance(x,y));
                
//                 // 到起始点的距离,临近node,直接算
//                 // next.g=node.g+5;
//                 // //到重点的距离
//                 // next.h=ouDistance(x,y);
//                 queue.add(next);
//                 graph[x][y]=graph[node.x][node.y]+1;
//             }
//         }
//     }

//     public static void main(String[] args){
//         Scanner scan=new Scanner(System.in);
//         int n = scan.nextInt();
//         while(n-->0){
//             for(int j=0;j<=1000;++j){
//                 Arrays.fill(graph[j],0);
//             }
//             int a1=scan.nextInt();
//             int a2=scan.nextInt();
//             b1=scan.nextInt();
//             b2=scan.nextInt();
//             double dis=ouDistance(a1,a2);
//             astar(new knight(a1,a2,0,dis,dis));
//             System.out.println(graph[b1][b2]);
//         }
//         scan.close();
//     }
// }


import java.util.*;

public class Main {
    static int b1, b2;
    static int[][] directs = new int[][]{
        {-2, -1}, {-2, 1}, {-1, 2}, {1, 2},
        {2, 1}, {2, -1}, {1, -2}, {-1, -2}
    };
    static int[][] graph = new int[1001][1001];

    // 定义 knight 类
    static class knight {
        int x, y;
        double g, h, f;

        // 4 参数构造函数
        public knight(int a, int b, double c, double d) {
            x = a;
            y = b;
            g = c;
            h = d;
            f = g + h;
        }

        public knight() {
        }
    }

    // 计算欧式距离时间会超限
    private static double ouDistance(int x, int y) {
        return (Math.abs(x - b1) * Math.abs(x - b1) + Math.abs(y - b2) * Math.abs(y - b2));
    }

    // A* 搜索算法
    private static void astar(knight k) {
        //PriorityQueue<knight> queue = new PriorityQueue<>((k1, k2) -> (int)(k1.f-k2.f));

        PriorityQueue<knight> queue = new PriorityQueue<>((k1, k2) -> Double.compare(k1.f, k2.f));
        queue.add(k);
        while (!queue.isEmpty()) {
            knight node = queue.poll();

            // 检查是否到达目标位置
            if (node.x == b1 && node.y == b2) {
                return;
            }

            // 遍历 8 个方向的 "马走日" 移动
            for (int[] d : directs) {
                int x = node.x + d[0];
                int y = node.y + d[1];

                // 检查边界和是否访问过
                if (x < 1 || y < 1 || x > 1000 || y > 1000 || graph[x][y] != 0) {
                    continue;
                }

                // 创建新的 knight 对象
                double g = node.g + 5;
                double h = ouDistance(x, y);
                knight next = new knight(x, y, g, h);

                // 更新 graph 数组并加入队列
                graph[x][y] = graph[node.x][node.y] + 1;
                queue.add(next);
            }
        }
    }

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        while (n-- > 0) {
            // 重置 graph 数组
            for (int j = 0; j <= 1000; ++j) {
                Arrays.fill(graph[j], 0);
            }

            int a1 = scan.nextInt();
            int a2 = scan.nextInt();
            b1 = scan.nextInt();
            b2 = scan.nextInt();

            // 初始化起点
            double dis = ouDistance(a1, a2);
            astar(new knight(a1, a2, 0, dis));

            // 输出结果
            System.out.println(graph[b1][b2]);
        }
        scan.close();
    }
}

或者堆写外面就创建一次时间也短一些:

import java.util.*;

class Main{
    static int b1,b2;

    private static int oushiDist(int x,int y){
        return (int)(Math.pow(x-b1,2)+Math.pow(y-b2,2));
    }

    static class knight implements Comparable<knight>{
        int x,y,g,h;
        int f;
        //知3得5
        public knight(int a,int b,int c){
            x=a;
            y=b;
            g=c;
            h=oushiDist(a,b);
            f=g+h;
        }

        @Override
        public int compareTo(knight o) {
            return this.f-o.f;
        }
    }
    //起点到x,y的距离
    static int[][] graph=new int[1001][1001];
    static int[][] directs=new int[][]{{-2,-1},{-1,-2},{1,-2},{2,-1},{2,1},{1,2},{-1,2},{-2,1}};
    //队列/栈:Deque,堆:PriorityQueue
    static PriorityQueue<knight> q=new PriorityQueue<>();

    private static void aStar(knight k){
        q.add(k);
        while(!q.isEmpty()){
            knight node=q.poll();
            // 检查是否到达目标位置
            if (node.x == b1 && node.y == b2) {
                return;
            }
            for(int[] d:directs){
                int nextx=node.x+d[0];
                int nexty=node.y+d[1];
                //超边界了 or 之前访过 忽略
                if(nextx<1 ||nexty<1 ||nextx>1000 ||nexty>1000 ||graph[nextx][nexty]!=0)continue;
                //不是比较选择入堆,都入堆
                knight next=new knight(nextx,nexty,node.g+5);
                q.add(next);
                graph[nextx][nexty]=graph[node.x][node.y]+1;
            }
        }
    }

    public static void main(String[] args){
        Scanner scan=new Scanner(System.in);
        int n=scan.nextInt();
        while(n-->0){
            for(int i=0;i<1001;++i){
                Arrays.fill(graph[i],0);
            }
            q.clear();

            int a1=scan.nextInt();
            int a2=scan.nextInt();
            b1=scan.nextInt();
            b2=scan.nextInt();
            aStar(new knight(a1,a2,0));
            System.out.println(graph[b1][b2]);
        }
        scan.close();
    }
}

最坏情况下,A * 退化成广搜,算法的时间复杂度 是 O(n * 2),n 为节点数量。

最佳情况,是从起点直接到终点,时间复杂度为 O(dlogd),d 为起点到终点的深度。

实际上 A * 的时间复杂度是介于 最优 和最坏 情况之间, 可以 非常粗略的认为 A * 算法的时间复杂度是 O(nlogn) ,n 为节点数量。

如果本题大家使用 曼哈顿距离 或者 切比雪夫距离 计算的话,可以提交试一试,有的最短路结果是并不是最短的。

A * 算法并不能保证一定是最短路,因为在设计 启发式函数的时候,要考虑 时间效率与准确度之间的一个权衡。

所以 在游戏开发设计中,保证运行效率的情况下,A * 算法中的启发式函数 设计往往不是最短路,而是接近最短路的 次短路设计

结束

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/933329.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Oracle 一键检查加强版本

支持更丰富了&#xff0c;代码也更乱了 实例个数 告警日志 实例状态 用户连接 活动会话 锁 集群状态 服务状态 磁盘空间 cpu mem 侦听及日志 单机、RAC Linux、AIX 11g、19c、23ai 多实例、多租户、ADG 依赖adrci配置正常&#xff0c;也可以改为 getAlert() 将脚本保存为j.…

开发者如何使用GCC提升开发效率Opencv操作

看此篇前请先阅读 https://blog.csdn.net/qq_20330595/article/details/144134160?spm=1001.2014.3001.5502 https://blog.csdn.net/qq_20330595/article/details/144134160?spm=1001.2014.3001.5502 https://blog.csdn.net/qq_20330595/article/details/144216351?spm=1001…

工具篇--GitHub Desktop 使用

文章目录 前言一、GitHub Desktop 的使用&#xff1a;1.1 通过官网下载GitHub Desktop和安装&#xff1a;1.2 安装和使用&#xff1a;1.2.1 填充自己的标识&#xff1a;1.2.3 克隆项目&#xff1a;1.2.4 git 常用忽略项配置&#xff1a; 二、代码的更新和提交&#xff1a;2.1 代…

PHP:将数据传递给Grid++Report模板进行打印

模板参考 这里使用的模板test111.grt参照进行生成 &#xff0c;需要确保字段对应才能将数据进行传递 GridReport:自定义模板设计&#xff08;自由表格使用&#xff09;&#xff0c;详细教程-CSDN博客https://blog.csdn.net/weixin_46001736/article/details/144315191?spm10…

Camp4-L2:LMDeploy 量化部署进阶实践

书生浦语大模型实战营第四期&#xff1a;LMDeploy 量化部署进阶实践 教程链接&#xff1a;https://github.com/InternLM/Tutorial/tree/camp4/docs/L2/LMDeploy视频链接&#xff1a;https://www.bilibili.com/video/BV18aUHY3EEG/?vd_sourceb96c7e6e6d1a48e73edafa36a36f1697…

spark-operaotr

1、系统架构 括如下几个组件: SparkApplication控制器, 该控制器用于创建、更新、删除SparkApplication对象,同时控制器还会监控相应的事件,执行相应的动作;Submission Runner, 负责调用spark-submit提交Spark作业, 作业提交的流程完全复用Spark on K8s的模式;Spark Pod Monit…

记录:ubuntu24.04源码安装nginx

一. 下载Nginx源码 两个地址二选一即可 Nginx官网Nginx官网 Github eg&#xff1a;nginx-1.27.3.tar.gz 下载到 ubuntu24.04 的 Downloads &#xff0c;解压 cd Downloads tar -zxvf nginx-1.27.3.tar.gz二. 编译安装 Note: 编译最好用 root 权限&#xff0c; 使用下面命令…

国产GPU中,VLLM0.5.0发布Qwen2.5-14B-Instruct-GPTQ-Int8模型,请求返回结果乱码

概述 国产GPU: DCU Z100 推理框架&#xff1a; vllm0.5.0 docker容器化部署 运行如下代码&#xff1a; python -m vllm.entrypoints.openai.api_server --model /app/models/Qwen2.5-14B-Instruct-GPTQ-Int8 --served-model-name qwen-gptq --trust-remote-code --enforce…

R155 VTA 认证对汽车入侵检测系统(IDS)合规要求

续接上集“浅谈汽车网络安全车辆型式认证&#xff08;VTA&#xff09;的现状和未来发展”&#xff0c;有许多读者小伙伴有联系笔者来确认相关的R155 VTA网络安全审核要求&#xff0c;基于此&#xff0c;笔者将针对 R155 VTA 每一条网络安全审核细则来具体展开。 今天就先从汽车…

Pac4j 学习笔记

随着互联网技术的飞速发展&#xff0c;网络安全问题日益凸显&#xff0c;企业信息安全与身份认证系统变得越来越重要&#xff0c;而且安全认证集成方案作为保障网络安全的重要一环&#xff0c;其研究与应用也至关重要。在这种背景下&#xff0c;Pac4j 作为一种流行的身份验证库…

5G CPE组成及功能介绍(二)

5G CPE 组成及功能介绍 5G CPE 将5G信号转换为Wi-Fi或有线信号, 其由5G基带芯片、主控处理器、WIFI、电源、天线、结构等多个部件组成。5G基带: 这是5G CPE中最核心的组件,负责接收和解码来自5G基站的信号,然后将这些数据转换成用户设备可以使用的格式。采用了先进的5G芯片…

微服务-seata分布式事务

1.简述 1.1.什么是分布式事务 事务&#xff1a;是应用程序中一系列严密的操作&#xff0c;所有操作必须成功完成&#xff0c;要么全部失败&#xff0c;ACID 特性。本地事务&#xff1a;关系型数据库中,由一组SQL组成的一个执行单元,该单元要么整体成功,要么整体失败&#xff…

flyway执行sql遇到变量执行报错解决

前两天在公司使用flyway工具执行sql时&#xff0c;开发写的sql里面有变量&#xff0c;于是这个flyway工具不识别这个变量直接报错&#xff0c;不接着往下执行了。报错信息如下&#xff1a; flyway工具执行sql报错 information: No value provided for placeholder: ${ep1} 于是…

k8s 为什么需要Pod?

Pod&#xff0c;是 Kubernetes 项目中最小的 API 对象&#xff0c;更加专业的说&#xff0c;Pod&#xff0c;是 Kubernetes 项目的原子调度单位。 Pod 是 Kubernetes 里的原子调度单位。这就意味着&#xff0c;Kubernetes 项目的调度器&#xff0c;是统一按照 Pod 而非容器的资…

IDEA 鼠标悬浮显示方法注释 javaDoc 及配置遇到的问题

方法详情&#xff1a; 鼠标悬浮时的效果&#xff1a; 设置方法&#xff1a; File -> Settings -> Editor -> Code Editing -> Quick Documentation,勾选红框中的选项 可能会遇到的问题&#xff1a; 如果不能选中&#xff0c;如下图 把下图的位置的选中项取消掉 选…

vscode CMakeLists中对opencv eigen的引用方法

CMakeLists.txt 项目模式&#xff08;只有一个main函数入口&#xff09; cmake_minimum_required(VERSION 3.5)project(vsin01 VERSION 0.1 LANGUAGES CXX)set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON)set(OpenCV_DIR G:/MinGW_Opencv/opencv4.10/opencv…

cocos creator接入字节跳动抖音小游戏JSAPI敏感词检测(进行文字输入,但输入敏感词后没有替换为*号)

今天更新了某个抖音小游戏的版本&#xff0c;增加了部分剧情&#xff0c;半天过后一条短信审核未通过&#xff0c;emmm…抖音总是能给开发者惊喜…打开电脑看看这次又整什么幺蛾子… 首先是一脸懵逼&#xff0c;后端早已接入了官方的内容安全检测能力了&#xff08;https://de…

基于单片机的中小水电站闸门控制系统(论文+源码)

1 系统总体设计 本次基于单片机的中小水电站闸门控制系统的设计&#xff0c;整体结构如图2.1所示。整个系统包括stm32单片机最小系统&#xff0c;电源&#xff0c;液晶&#xff0c;电机&#xff0c;闸门开度检测模块&#xff0c;水位检测模块&#xff0c;温度传感器&#xff0…

证明网络中的流形成一个凸集

证明网络中的流形成一个凸集 步骤1&#xff1a;定义和符号步骤2&#xff1a;线性组合步骤3&#xff1a;验证容量限制步骤4&#xff1a;验证流量守恒结论示例代码&#xff08;C语言&#xff09; 在网络流理论中&#xff0c;一个流 f f f 是定义在网络图的边集上的一种函数&…

【贪心算法】贪心算法五

贪心算法五 1.跳跃游戏 II2.跳跃游戏3.加油站3.单调递增的数字 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603; 1.跳跃游戏 II 题目链接&…