图论-代码随想录刷题记录[JAVA]

news2024/11/16 0:05:51

文章目录

  • 前言
  • 深度优先搜索理论基础
  • 所有可达路径
  • 岛屿数量
  • 岛屿最大面积
  • 孤岛的总面积
  • 沉默孤岛
  • Floyd 算法
  • dijkstra(朴素版)
  • 最小生成树之prim
  • kruskal算法


前言

新手小白记录第一次刷代码随想录
1.自用 抽取精简的解题思路 方便复盘
2.代码尽量多加注释
3.记录踩坑
4.边刷边记录,更有成就感!
5.解题思路绝大部分来自代码随想录【仅自用 无商用!!!】


深度优先搜索理论基础

代码框架

void dfs(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本节点所连接的其他节点) {
        处理节点;
        dfs(图,选择的节点); // 递归
        回溯,撤销处理结果
    }
}

所有可达路径

【题目描述】

给定一个有 n 个节点的有向无环图,节点编号从 1 到 n。请编写一个函数,找出并返回所有从节点 1 到节点 n 的路径。每条路径应以节点编号的列表形式表示。

【输入描述】

第一行包含两个整数 N,M,表示图中拥有 N 个节点,M 条边

后续 M 行,每行包含两个整数 s 和 t,表示图中的 s 节点与 t 节点中有一条路径

【输出描述】

输出所有的可达路径,路径中所有节点的后面跟一个空格,每条路径独占一行,存在多条路径,路径输出的顺序可任意。

如果不存在任何一条路径,则输出 -1。

注意输出的序列中,最后一个节点后面没有空格! 例如正确的答案是 1 3 5,而不是 1 3 5, 5后面没有空格!

【输入示例】

5 5
1 3
3 5
1 2
2 4
4 5
【输出示例】

1 3 5
1 2 4 5

邻接矩阵

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {
    static List<List<Integer>> result = new ArrayList<>(); // 收集符合条件的路径
    static List<Integer> path = new ArrayList<>(); // 1节点到终点的路径

    public static void dfs(int[][] graph, int x, int n) {
        // 当前遍历的节点x 到达节点n
        if (x == n) { // 找到符合条件的一条路径
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i = 1; i <= n; i++) { // 遍历节点x链接的所有节点
            if (graph[x][i] == 1) { // 找到 x链接的节点
                path.add(i); // 遍历到的节点加入到路径中来
                dfs(graph, i, n); // 进入下一层递归
                path.remove(path.size() - 1); // 回溯,撤销本节点
            }
        }
    }

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

        // 节点编号从1到n,所以申请 n+1 这么大的数组
        int[][] graph = new int[n + 1][n + 1];

        for (int i = 0; i < m; i++) {
            int s = scanner.nextInt();
            int t = scanner.nextInt();
            // 使用邻接矩阵表示无向图,1 表示 s 与 t 是相连的
            graph[s][t] = 1;
        }

        path.add(1); // 无论什么路径已经是从1节点出发
        dfs(graph, 1, n); // 开始遍历

        // 输出结果
        if (result.isEmpty()) System.out.println(-1);
        for (List<Integer> pa : result) {
            for (int i = 0; i < pa.size() - 1; i++) {
                System.out.print(pa.get(i) + " ");
            }
            System.out.println(pa.get(pa.size() - 1));
        }
    }
}

邻接表

import java.util.*;

public class Main {
    static List<List<Integer>> res = new ArrayList<>();
    static List<Integer> path = new ArrayList<>();
    
    public static void dfs(List<LinkedList<Integer>> graph, int now, int n) {
        // 终止条件:找到一条从1到n的路径
        if (now == n) {
            res.add(new ArrayList<>(path));
            return;
        }
        // 遍历当前节点的所有邻接节点
        for (int i : graph.get(now)) {
            path.add(i);  // 添加当前节点到路径中
            dfs(graph, i, n);  // 递归探索下一节点
            path.remove(path.size() - 1);  // 回溯,移除当前节点
        }
    }
    
    public static void main(String args[]) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();  // 节点数
        int m = sc.nextInt();  // 边数
     
        // 初始化图的邻接表
        List<LinkedList<Integer>> graph = new ArrayList<>(n + 1);
        for (int i = 0; i <= n; i++) {
            graph.add(new LinkedList<>());
        }

        // 构建图的邻接表
        int a, b;
        while (m-- > 0) {
            a = sc.nextInt();
            b = sc.nextInt();
            graph.get(a).add(b);  // 从a到b的边
        }

        // 从节点1开始路径搜索
        path.add(1);  // 初始路径包含节点1
        dfs(graph, 1, n);

        // 如果没有路径
        if (res.isEmpty()) {
            System.out.println(-1);  // 没有路径
        } else {
            // 打印所有路径
            for (List<Integer> re : res) {
                for (int i = 0; i < re.size() - 1; i++) {
                    System.out.print(re.get(i) + " ");
                }
                System.out.println(re.get(re.size() - 1));  // 打印路径的最后一个节点
            }
        }
    }
}

岛屿数量

给定一个由 1(陆地)和 0(水)组成的矩阵,你需要计算岛屿的数量。岛屿由水平方向或垂直方向上相邻的陆地连接而成,并且四周都是水域。你可以假设矩阵外均被水包围。

输入描述:

第一行包含两个整数 N, M,表示矩阵的行数和列数。

后续 N 行,每行包含 M 个数字,数字为 1 或者 0。

输出描述:

输出一个整数,表示岛屿的数量。如果不存在岛屿,则输出 0。

输入示例:

4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
输出示例:

3
数据范围:

1 <= N, M <= 50

深搜版

import java.util.Scanner;

public class Main {
    static int cnt = 0; // 用于计数岛屿数量
    static int direct[][] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; // 四个方向的移动

    // 深度优先搜索
    public static void dfs(int graph[][], int x, int y, boolean visited[][]) {
        // 遍历四个方向
        for (int i = 0; i < 4; i++) {
            int nextX = x + direct[i][0];  // 下一个位置的x坐标
            int nextY = y + direct[i][1];  // 下一个位置的y坐标

            // 判断是否越界
            if (nextX < 0 || nextY < 0 || nextX >= graph.length || nextY >= graph[0].length) {
                continue; // 如果越界,跳过
            }

            // 如果当前位置是陆地并且未访问过,递归搜索
            if (!visited[nextX][nextY] && graph[nextX][nextY] == 1) {
                visited[nextX][nextY] = true; // 标记为已访问
                dfs(graph, nextX, nextY, visited); // 递归搜索
            }
        }
    }

    public static void main(String[] args) {
        int n, m, a;
        Scanner sc = new Scanner(System.in);

        n = sc.nextInt(); // 行数
        m = sc.nextInt(); // 列数

        int[][] graph = new int[n][m]; // 地图
        boolean[][] visited = new boolean[n][m]; // 记录每个位置是否已访问

        // 读取地图
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                a = sc.nextInt();
                graph[i][j] = a; // 1表示陆地,0表示水域
            }
        }

        // 遍历整个图,每当找到一个未访问的陆地,执行深度优先搜索
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                // 找到一个未访问的陆地,开始深度优先搜索
                if (!visited[i][j] && graph[i][j] == 1) {
                    cnt++; // 找到一个岛屿
                    visited[i][j] = true; // 标记为已访问
                    dfs(graph, i, j, visited); // 递归搜索整个岛屿
                }
            }
        }

        // 输出岛屿的数量
        System.out.println(cnt);
    }
}

另一种写终止条件的写法

public static void dfs(int[][] grid, boolean[][] visited, int x, int y) {
        // 终止条件:访问过的节点 或者 遇到海水(grid[x][y] == 0)
        if (visited[x][y] || grid[x][y] == 0) {
            return;
        }
        visited[x][y] = true; // 标记当前位置为已访问
        // 遍历四个方向
        for (int i = 0; i < 4; i++) {
            int nextX = x + dir[i][0];
            int nextY = y + dir[i][1];
            // 检查是否越界
            if (nextX < 0 || nextX >= grid.length || nextY < 0 || nextY >= grid[0].length) {
                continue;
            }
            // 递归调用 DFS
            dfs(grid, visited, nextX, nextY);
        }
    }

广搜版

  • 不少同学用广搜做这道题目的时候,超时了。 这里有一个广搜中很重要的细节:
  • 根本原因是只要 加入队列就代表 走过,就需要标记,而不是从队列拿出来的时候再去标记走过。
  • 很多同学可能感觉这有区别吗?
  • 如果从队列拿出节点,再去标记这个节点走过,就会发生下图所示的结果,会导致很多节点重复加入队列。

在这里插入图片描述

import java.util.*;

public class Main {
    // 定义四个方向的偏移量:下、右、上、左
    public static int[][] dir = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};// 下右上左

    // 自定义pair类,用于存储坐标
    static class pair {
        int first, second;
        pair(int x, int y) {
            this.first = x;
            this.second = y;
        }
    }

    // BFS遍历函数
    public static void bfs(int[][] grid, boolean[][] visited, int x, int y) {
        Queue<pair> queue = new LinkedList<pair>();  // 定义坐标队列
        queue.add(new pair(x, y)); // 入队当前坐标
        visited[x][y] = true; // 标记当前位置为已访问
        while (!queue.isEmpty()) {
            int curX = queue.peek().first;  // 获取队列头的X坐标
            int curY = queue.poll().second; // 获取队列头的Y坐标并出队(poll是把对头元素出队了)
            // 遍历四个方向
            for (int i = 0; i < 4; i++) {
                // 计算下一个坐标
                int nextX = curX + dir[i][0];
                int nextY = curY + dir[i][1];
                // 检查越界
                if (nextX < 0 || nextX >= grid.length || nextY < 0 || nextY >= grid[0].length) {
                    continue;
                }
                // 如果没有访问过并且该点是陆地(值为1),则入队
                if (!visited[nextX][nextY] && grid[nextX][nextY] == 1) {
                    queue.add(new pair(nextX, nextY));
                    visited[nextX][nextY] = true; // 标记为已访问
                }
            }
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 输入网格的行数和列数
        int m = sc.nextInt();
        int n = sc.nextInt();
        int[][] grid = new int[m][n];
        boolean[][] visited = new boolean[m][n];
        int ans = 0;

        // 输入网格的每个值(0为水域,1为陆地)
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                grid[i][j] = sc.nextInt();
            }
        }

        // 遍历网格,查找所有的岛屿
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                // 如果该点是陆地并且未访问过,则说明发现一个新的岛屿
                if (!visited[i][j] && grid[i][j] == 1) {
                    ans++; // 岛屿数量加一
                    bfs(grid, visited, i, j); // 通过BFS将该岛屿所有的陆地标记为已访问
                }
            }
        }

        // 输出岛屿数量
        System.out.println(ans);
    }
}

岛屿最大面积

题目描述

给定一个由 1(陆地)和 0(水)组成的矩阵,计算岛屿的最大面积。岛屿面积的计算方式为组成岛屿的陆地的总数。岛屿由水平方向或垂直方向上相邻的陆地连接而成,并且四周都是水域。你可以假设矩阵外均被水包围。

输入描述

第一行包含两个整数 N, M,表示矩阵的行数和列数。后续 N 行,每行包含 M 个数字,数字为 1 或者 0,表示岛屿的单元格。

输出描述

输出一个整数,表示岛屿的最大面积。如果不存在岛屿,则输出 0。

输入示例

4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
输出示例

4

写在前面

  • count是类成员变量,如果不加 static,它们将被视为实例变量,只有通过实例化 Main 类才能访问它们。
  • bfs 方法是静态方法,它可以直接通过类名 Main.bfs() 调用。如果不将其设为 static,它就需要依赖于一个 Main 类的实例来调用。
  • 如果不加 static,访问count 以及调用 bfs 会出现问题,因为:count 是实例变量,而 bfs 是静态方法。静态方法只能访问静态变量和静态方法。
  • 即使你没有实例化 Main 类,你也希望在 main 方法中访问它们,这就要求count 也必须是静态的。

dfs

写法一,dfs只处理下一个节点,即在主函数遇到岛屿就计数为1,dfs处理接下来的相邻陆地

import java.util.*;

public class Main {
    // 定义四个方向的偏移量:右、下、左、上
    public static int[][] dir = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
    static int count; // 记录每次DFS访问的陆地数量

    // 深度优先搜索 (DFS) 函数
    public static void dfs(int[][] grid, boolean[][] visited, int x, int y) {
        for (int i = 0; i < 4; i++) {
            int nextX = x + dir[i][0];
            int nextY = y + dir[i][1];
            
            // 检查越界
            if (nextX < 0 || nextX >= grid.length || nextY < 0 || nextY >= grid[0].length) {
                continue;
            }

            // 如果该位置没有访问过且是陆地,则继续DFS
            if (!visited[nextX][nextY] && grid[nextX][nextY] == 1) {
                visited[nextX][nextY] = true;
                count++; // 增加当前岛屿的陆地数量
                dfs(grid, visited, nextX, nextY); // 递归访问相邻的陆地
            }
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 输入网格的行数n和列数m
        int n = sc.nextInt();
        int m = sc.nextInt();
        
        // 创建网格并填充输入数据
        int[][] grid = new int[n][m];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                grid[i][j] = sc.nextInt();
            }
        }

        // 创建一个visited数组,用来标记访问过的位置
        boolean[][] visited = new boolean[n][m];
        int result = 0; // 最终记录最大岛屿面积

        // 遍历每一个格子
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                // 如果当前格子是陆地并且未被访问过,进行DFS
                if (!visited[i][j] && grid[i][j] == 1) {
                    count = 1;  // 这里遇到陆地了,先计数1
                    visited[i][j] = true;
                    dfs(grid, visited, i, j); // 递归访问与当前陆地相连的陆地
                    result = Math.max(result, count); // 更新最大岛屿面积
                }
            }
        }

        // 输出结果
        System.out.println(result);
    }
}

写法二,dfs处理当前节点,即在主函数遇到岛屿就计数为0,dfs处理接下来的全部陆地

// 版本二
import java.util.Scanner;

public class Main {
    // 定义四个方向的偏移量:右、下、左、上
    public static int[][] dir = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
    static int count; // 记录每次DFS访问的陆地数量

    // 深度优先搜索 (DFS) 函数
    public static void dfs(int[][] grid, boolean[][] visited, int x, int y) {
        // 终止条件:访问过的节点 或者 遇到海水
        if (visited[x][y] || grid[x][y] == 0) return;
        
        visited[x][y] = true; // 标记当前位置为已访问
        count++; // 每访问到一个陆地,计数+1

        // 遍历四个方向
        for (int i = 0; i < 4; i++) {
            int nextX = x + dir[i][0];
            int nextY = y + dir[i][1];
            
            // 检查越界
            if (nextX < 0 || nextX >= grid.length || nextY < 0 || nextY >= grid[0].length) {
                continue;
            }

            // 递归调用 DFS
            dfs(grid, visited, nextX, nextY);
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        
        // 输入网格的行数n和列数m
        int n = sc.nextInt();
        int m = sc.nextInt();
        
        // 创建网格并填充输入数据
        int[][] grid = new int[n][m];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                grid[i][j] = sc.nextInt();
            }
        }

        // 创建一个visited数组,用来标记访问过的位置
        boolean[][] visited = new boolean[n][m];
        int result = 0; // 最终记录最大岛屿面积

        // 遍历每一个格子
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                // 如果当前格子是陆地并且未被访问过,进行DFS
                if (!visited[i][j] && grid[i][j] == 1) {
                    count = 0;  // 遇到陆地时先计数为0,进入DFS后开始从1计数
                    dfs(grid, visited, i, j); // 递归访问与当前陆地相连的陆地
                    result = Math.max(result, count); // 更新最大岛屿面积
                }
            }
        }

        // 输出结果
        System.out.println(result);
    }
}


大家通过注释可以发现,两种写法,版本一,在主函数遇到陆地就计数为1,接下来的相邻陆地都在dfs中计算。

版本二 在主函数遇到陆地 计数为0,也就是不计数,陆地数量都去dfs里做计算。

bfs的代码省略

孤岛的总面积

目描述

给定一个由 1(陆地)和 0(水)组成的矩阵,岛屿指的是由水平或垂直方向上相邻的陆地单元格组成的区域,且完全被水域单元格包围。孤岛是那些位于矩阵内部、所有单元格都不接触边缘的岛屿。

现在你需要计算所有孤岛的总面积,岛屿面积的计算方式为组成岛屿的陆地的总数。

输入描述

第一行包含两个整数 N, M,表示矩阵的行数和列数。之后 N 行,每行包含 M 个数字,数字为 1 或者 0。

输出描述

输出一个整数,表示所有孤岛的总面积,如果不存在孤岛,则输出 0。

输入示例

4 5
1 1 0 0 0
1 1 0 0 0
0 0 1 0 0
0 0 0 1 1
输出示例:


沉默孤岛

思路
本题要求找到不靠边的陆地面积,那么我们只要从周边找到陆地然后 通过 dfs或者bfs 将周边靠陆地且相邻的陆地都变成海洋,然后再去重新遍历地图 统计此时还剩下的陆地就可以了。

Floyd 算法

【题目描述】

小明喜欢去公园散步,公园内布置了许多的景点,相互之间通过小路连接,小明希望在观看景点的同时,能够节省体力,走最短的路径。

给定一个公园景点图,图中有 N 个景点(编号为 1 到 N),以及 M 条双向道路连接着这些景点。每条道路上行走的距离都是已知的。

小明有 Q 个观景计划,每个计划都有一个起点 start 和一个终点 end,表示他想从景点 start 前往景点 end。由于小明希望节省体力,他想知道每个观景计划中从起点到终点的最短路径长度。 请你帮助小明计算出每个观景计划的最短路径长度。

【输入描述】

第一行包含两个整数 N, M, 分别表示景点的数量和道路的数量。

接下来的 M 行,每行包含三个整数 u, v, w,表示景点 u 和景点 v 之间有一条长度为 w 的双向道路。

接下里的一行包含一个整数 Q,表示观景计划的数量。

接下来的 Q 行,每行包含两个整数 start, end,表示一个观景计划的起点和终点。

【输出描述】

对于每个观景计划,输出一行表示从起点到终点的最短路径长度。如果两个景点之间不存在路径,则输出 -1。

【输入示例】

7 3 1 2 4 2 5 6 3 6 8 2 1 2 2 3

【输出示例】

4 -1

【提示信息】

从 1 到 2 的路径长度为 4,2 到 3 之间并没有道路。

1 <= N, M, Q <= 1000.

思路

Floyd算法核心思想是动态规划。

  • 例如我们再求节点1 到 节点9 的最短距离,用二维数组来表示即:grid[1][9],如果最短距离是10 ,那就是 grid[1][9] =10。

  • 那 节点1 到 节点9 的最短距离 是不是可以由 节点1 到节点5的最短距离 + 节点5到节点9的最短距离组成呢? 即 grid[1][9] = grid[1][5] + grid[5][9]

  • 节点1 到节点5的最短距离 是不是可以有 节点1 到 节点3的最短距离 + 节点3 到 节点5 的最短距离组成呢? 即 grid[1][5] = grid[1][3] + grid[3][5]

  • 以此类推,节点1 到 节点3的最短距离 可以由更小的区间组成。那么这样我们是不是就找到了,子问题推导求出整体最优方案的递归关系呢。

  • 节点1 到 节点9 的最短距离 可以由 节点1 到节点5的最短距离 + 节点5到节点9的最短距离组成, 也可以有 节点1 到节点7的最短距离 + 节点7 到节点9的最短距离的距离组成。

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int n = sc.nextInt();  // 顶点数
        int m = sc.nextInt();  // 边数
        
        // 初始化距离矩阵,最大值设置为10005
        final int INF = 10005;
        int[][] grid = new int[n + 1][n + 1];

        // 初始化 grid 数组
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if (i != j) {
                    grid[i][j] = INF;
                }
            }
        }

        // 输入边信息
        for (int i = 0; i < m; i++) {
            int p1 = sc.nextInt();
            int p2 = sc.nextInt();
            int val = sc.nextInt();
            grid[p1][p2] = val;
            grid[p2][p1] = val;  // 双向图
        }

        // Floyd-Warshall 算法
        //注意k要放在最外层
        for (int k = 1; k <= n; k++) {
            for (int i = 1; i <= n; i++) {
                for (int j = 1; j <= n; j++) {
                    if (grid[i][k] + grid[k][j] < grid[i][j]) {
                        grid[i][j] = grid[i][k] + grid[k][j];
                    }
                }
            }
        }

        // 输出查询结果
        int z = sc.nextInt();  // 查询次数
        while (z-- > 0) {
            int start = sc.nextInt();
            int end = sc.nextInt();
            if (grid[start][end] == INF) {
                System.out.println(-1);
            } else {
                System.out.println(grid[start][end]);
            }
        }
        
        sc.close();  // 关闭Scanner
    }
}


dijkstra(朴素版)

【题目描述】

小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。

小明的起点是第一个车站,终点是最后一个车站。然而,途中的各个车站之间的道路状况、交通拥堵程度以及可能的自然因素(如天气变化)等不同,这些因素都会影响每条路径的通行时间。

小明希望能选择一条花费时间最少的路线,以确保他能够尽快到达目的地。

【输入描述】

第一行包含两个正整数,第一个正整数 N 表示一共有 N 个公共汽车站,第二个正整数 M 表示有 M 条公路。

接下来为 M 行,每行包括三个整数,S、E 和 V,代表了从 S 车站可以单向直达 E 车站,并且需要花费 V 单位的时间。

【输出描述】

输出一个整数,代表小明从起点到终点所花费的最小时间。

思路

  • 第一步,选源点到哪个节点近且该节点未被访问过
  • 第二步,该最近节点被标记访问过
  • 第三步,更新非访问节点到源点的距离(即更新minDist数组
  • minDist数组 用来记录 每一个节点距离源点的最小距离。
  • 示例中节点编号是从1开始,所以为了让大家看的不晕,minDist数组下标我也从 1 开始计数,下标0 就不使用了,这样 下标和节点标号就可以对应上了,避免大家搞混

模拟过程

0、初始化

minDist数组数值初始化为int最大值。

这里在强点一下 minDist数组的含义:记录所有节点到源点的最短路径,那么初始化的时候就应该初始为最大值,这样才能在后续出现最短路径的时候及时更新。
代码随想录朴素版dijkstra
源点(节点1) 到自己的距离为0,所以 minDist[1] = 0

此时所有节点都没有被访问过,所以 visited数组都为0

  1. 模拟过程

以下为dijkstra 三部曲

1.1 第一次模拟

1、选源点到哪个节点近且该节点未被访问过

源点距离源点最近,距离为0,且未被访问。

2、该最近节点被标记访问过

标记源点访问过

3、更新非访问节点到源点的距离(即更新minDist数组) ,如图:
在这里插入图片描述
更新 minDist数组,即:源点(节点1) 到 节点2 和 节点3的距离。

源点到节点2的最短距离为1,小于原minDist[2]的数值max,更新minDist[2] = 1
源点到节点3的最短距离为4,小于原minDist[3]的数值max,更新minDist[3] = 4

1.2 第二次模拟

1、选源点到哪个节点近且该节点未被访问过

未访问过的节点中,源点到节点2距离最近,选节点2

2、该最近节点被标记访问过

节点2被标记访问过

3、更新非访问节点到源点的距离(即更新minDist数组) ,如图:
在这里插入图片描述
更新 minDist数组,即:源点(节点1) 到 节点6 、 节点3 和 节点4的距离。

以后的过程以此类推

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // 输入节点数 n 和边数 m
        int n = sc.nextInt();
        int m = sc.nextInt();

        // 定义一个邻接矩阵,初始化为一个很大的数
        final int INF = Integer.MAX_VALUE;
        int[][] grid = new int[n + 1][n + 1];

        // 初始化 grid 为 INF,表示没有直接路径
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if (i != j) {
                    grid[i][j] = INF;
                }
            }
        }

        // 输入边的信息
        for (int i = 0; i < m; i++) {
            int p1 = sc.nextInt();
            int p2 = sc.nextInt();
            int val = sc.nextInt();
            grid[p1][p2] = val;
        }

        // 设置起点和终点
        int start = 1;
        int end = n;

        // 存储从源点到每个节点的最短距离
        int[] minDist = new int[n + 1];
        // 记录顶点是否被访问过
        boolean[] visited = new boolean[n + 1];

        // 初始化最短距离数组,起始点到自身的距离为0,其他为INF
        for (int i = 1; i <= n; i++) {
            minDist[i] = INF;
        }
        minDist[start] = 0;

        // 遍历所有节点,执行Dijkstra算法
        for (int i = 1; i <= n; i++) {
            int minVal = INF;
            int cur = -1;

            // 选择距离起点最近且未访问过的节点
            for (int v = 1; v <= n; v++) {
                if (!visited[v] && minDist[v] < minVal) {
                    minVal = minDist[v];
                    cur = v;
                }
            }

            // 如果当前节点无法访问,则跳出循环(即剩下的节点不可达)
            if (cur == -1) break;

            visited[cur] = true; // 标记该节点已被访问

            // 更新非访问节点到源点的最短距离
            for (int v = 1; v <= n; v++) {
                if (!visited[v] && grid[cur][v] != INF && minDist[cur] + grid[cur][v] < minDist[v]) {
                    minDist[v] = minDist[cur] + grid[cur][v];
                }
            }
        }

        // 输出结果,如果终点不可达,输出 -1
        if (minDist[end] == INF) {
            System.out.println(-1);
        } else {
            System.out.println(minDist[end]);
        }

        sc.close(); // 关闭Scanner
    }
}

最小生成树之prim

卡码网:53.寻宝

题目描述:

在世界的某个区域,有一些分散的神秘岛屿,每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路,方便运输。

不同岛屿之间,路途距离不同,国王希望你可以规划建公路的方案,如何可以以最短的总公路距离将 所有岛屿联通起来。

给定一张地图,其中包括了所有的岛屿,以及它们之间的距离。以最小化公路建设长度,确保可以链接到所有岛屿。

输入描述:

第一行包含两个整数V 和 E,V代表顶点数,E代表边数 。顶点编号是从1到V。例如:V=2,一个有两个顶点,分别是1和2。

接下来共有 E 行,每行三个整数 v1,v2 和 val,v1 和 v2 为边的起点和终点,val代表边的权值。

输出描述:

输出联通所有岛屿的最小路径总距离

输入示例:

7 11
1 2 1
1 3 1
1 5 2
2 6 1
2 4 2
2 3 2
3 4 1
4 5 1
5 6 2
5 7 1
6 7 1
输出示例:

6

思路

  • 第一步,选距离生成树最近节点

  • 第二步,最近节点加入生成树

  • 第三步,更新非生成树节点到生成树的距离(即更新minDist数组)

  • minDist数组用来记录 每一个节点距离最小生成树的最近距离。

  • 示例中节点编号是从1开始,minDist数组下标也从 1 开始计数。

初始状态

minDist 数组 里的数值初始化为 最大数,因为本题 节点距离不会超过 10000,所以 初始化最大数为 10001就可以。

现在 还没有最小生成树,默认每个节点距离最小生成树是最大的,这样后面我们在比较的时候,发现更近的距离,才能更新到 minDist 数组上。
在这里插入图片描述

模拟过程(只模拟两轮)


第一轮

1、prim三部曲,第一步:选距离生成树最近节点

选择距离最小生成树最近的节点,加入到最小生成树,刚开始还没有最小生成树,所以随便选一个节点加入就好(因为每一个节点一定会在最小生成树里,所以随便选一个就好),那我们选择节点1 (符合遍历数组的习惯,第一个遍历的也是节点1)

2、prim三部曲,第二步:最近节点加入生成树

此时 节点1 已经算最小生成树的节点。

3、prim三部曲,第三步:更新非生成树节点到生成树的距离(即更新minDist数组)

在这里插入图片描述
注意图中我标记了 minDist数组里更新的权值,是哪两个节点之间的权值,例如 minDist[2] =1 ,这个 1 是 节点1 与 节点2 之间的连线,清楚这一点对最后我们记录 最小生成树的权值总和很重要。

第二轮
1、prim三部曲,第一步:选距离生成树最近节点

选取一个距离 最小生成树(节点1) 最近的非生成树里的节点,节点2,3,5 距离 最小生成树(节点1) 最近,选节点 2(其实选 节点3或者节点2都可以,距离一样的)加入最小生成树。

2、prim三部曲,第二步:最近节点加入生成树

此时 节点1 和 节点2,已经是最小生成树的节点。

3、prim三部曲,第三步:更新非生成树节点到生成树的距离(即更新minDist数组)

接下来,我们要更新节点距离最小生成树的距离,如图:
在这里插入图片描述

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int v = scanner.nextInt();
        int e = scanner.nextInt();

        // 初始化邻接矩阵,所有值初始化为一个大值,表示无穷大
        int[][] grid = new int[v + 1][v + 1];
        for (int i = 1; i <= v; i++) {
            Arrays.fill(grid[i], 10001);
        }

        // 读取边的信息并填充邻接矩阵
        for (int i = 0; i < e; i++) {
            int x = scanner.nextInt();
            int y = scanner.nextInt();
            int k = scanner.nextInt();
            grid[x][y] = k;
            grid[y][x] = k;
        }

        // 所有节点到最小生成树的最小距离
        int[] minDist = new int[v + 1];
        Arrays.fill(minDist, 10001);

        // 记录节点是否在树里
        boolean[] isInTree = new boolean[v + 1];

        // Prim算法主循环
         只需要循环v-1次建立v-1条边
        for (int i = 1; i < v; i++) {
            int cur = -1;// 用于记录距离生成树最近的节点
            int minVal = Integer.MAX_VALUE; // 记录最短距离
			 
            // 选择距离生成树最近的节点
            for (int j = 1; j <= v; j++) {
            	 // 如果这个点不在生成树里面,且它的距离小于当前最小值
                if (!isInTree[j] && minDist[j] < minVal) {
                    minVal = minDist[j];
                    cur = j;
                }
            }

            // 将最近的节点加入生成树
            isInTree[cur] = true;

            // 更新非生成树节点到生成树的距离
            for (int j = 1; j <= v; j++) {
            	//当前cur节点比较
                if (!isInTree[j] && grid[cur][j] < minDist[j]) {
                    minDist[j] = grid[cur][j];
                }
            }
        }
        

        // 统计结果
        int result = 0;
        for (int i = 2; i <= v; i++) {
            result += minDist[i];// 从2开始,跳过起始节点
        }
        System.out.println(result);
        scanner.close();
    }
}

kruskal算法

  • 题目同上题,找最小生成树。

思路

  • prim 算法是维护节点的集合,而 Kruskal 是维护边的集合。
  • 边的权值排序,因为要优先选最小的边加入到生成树里
  • 遍历排序后的边
    • 如果边首尾的两个节点在同一个集合,说明如果连上这条边图中会出现环
    • 如果边首尾的两个节点不在同一个集合,加入到最小生成树,并把两个节点加入同一个集合

模拟
在这里插入图片描述
排序后的边顺序为[(1,2) (4,5) (1,3) (2,6) (3,4) (6,7) (5,7) (1,5) (3,2) (2,4) (5,6)]

(1,2) 表示节点1 与 节点2 之间的边。权值相同的边,先后顺序无所谓。

开始从头遍历排序后的边。

选边(1,2),节点1 和 节点2 不在同一个集合,所以生成树可以添加边(1,2),并将 节点1,节点2 放在同一个集合。
在这里插入图片描述选边(4,5),节点4 和 节点 5 不在同一个集合,生成树可以添加边(4,5) ,并将节点4,节点5 放到同一个集合。
在这里插入图片描述

在上面的讲解中,看图的话 大家知道如何判断 两个节点 是否在同一个集合(是否有绿色的线连在一起),以及如何把两个节点加入集合(就在图中把两个节点连上)

  • 但在代码中,如果将两个节点加入同一个集合,又如何判断两个节点是否在同一个集合呢?
    • 用并查集
import java.util.*;

class Edge {
    int l, r, val;

    Edge(int l, int r, int val) {
        this.l = l;
        this.r = r;
        this.val = val;
    }
}
public class Main {
    private static int n = 10001;
    private static int[] father = new int[n];
	 // 并查集初始化
    public static void init() {
        for (int i = 0; i < n; i++) {
            father[i] = i;
        }
    }

	// 并查集的查找操作
    public static int find(int u) {
        if (u == father[u]) return u;
        return father[u] = find(father[u]);
    }
	 public static void join(int u, int v) {
        u = find(u);
        v = find(v);
        if (u == v) return;
        father[v] = u;
    }
	 public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int v = scanner.nextInt();
        int e = scanner.nextInt();
		List<Edge> edges = new ArrayList<>();
		int result_val = 0;
		for (int i = 0; i < e; i++) {
            int v1 = scanner.nextInt();
            int v2 = scanner.nextInt();
            int val = scanner.nextInt();
            edges.add(new Edge(v1, v2, val));
        }
        //对边进行排序
         edges.sort(Comparator.comparingInt(edge -> edge.val));
		  // 并查集初始化
        init();

        // 从头开始遍历边
        for (Edge edge : edges) {
            int x = find(edge.l);
            int y = find(edge.r);

            if (x != y) {
                result_val += edge.val;
                join(x, y);
            }
        }
        System.out.println(result_val);
        scanner.close();
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

测试自动化如何和业务流程结合?

测试自动化框架固然重要&#xff0c;但是最终自动化的目的都是为了业务服务的。 那测试自动化如何对业务流程产生积极影响&#xff1f; 业务流程的重要性 测试自动化项目并非孤立存在&#xff0c;其生命周期与被测试的应用程序紧密相关。项目的价值在于被整个开发团队所使用&a…

大模型基础BERT——Transformers的双向编码器表示

大模型基础BERT——Transformers的双向编码器表示 整体概况 BERT&#xff1a;用于语言理解的深度双向Transform的预训练 论文题目&#xff1a;BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding Bidirectional Encoder Representations from…

Leetcode 56-合并区间

以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 //按左边界排序 //startintervals[i][0],endintervals…

【golang-技巧】-线上死锁问题排查-by pprof

1.背景 由于目前项目使用 cgo golang 本地不能debug, 发生死锁问题&#xff0c;程序运行和期待不一致&#xff0c;通过日志排查可以大概率找到 阻塞范围&#xff0c;但是不能找到具体问题在哪里&#xff0c;同时服务器 通过k8s daemonset 部署没有更好的方式暴露端口 获取ppr…

7天用Go从零实现分布式缓存GeeCache(总结)

1. Lru包 1.1 lru算法简要概述 &#xff08;作者&#xff1a;豆豉辣椒炒腊肉/链接&#xff1a;https://juejin.cn/post/6844904049263771662&#xff09; LRU算法全称是最近最少使用算法&#xff08;Least Recently Use&#xff09;&#xff0c;广泛的应用于缓存机制中。当缓…

oracle查询字段类型长度等字段信息

1.查询oracle数据库的字符集 SELECT * FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER NLS_CHARACTERSET; 2.查询字段长度类型 SELECT * FROM user_tab_columns WHERE table_name user AND COLUMN_NAME SNAME 请确保将user替换为您想要查询的表名。sname为字段名 这里的字…

前端面试笔试(一)

目录 一、数据结构算法等综合篇 1.直接插入排序&#xff0c;有n个元素待排序&#xff0c;则最多进行多少次比较 2.软件测试中评估网络性能的关键指标有哪些 3.哈希查找 4.内存保护 二、代码输出篇 1.promise中throw new Error输出 2.Promise.all 3.this关键字,obj.get…

iOS 18.2 重磅更新:6个大动作

根据外媒报道&#xff0c;iOS 18.2迎来重磅更新&#xff0c;将带来6个大动作&#xff0c;这是一次非常实用的更新。不过要注意的是&#xff0c;其中涉及到AI的功能&#xff0c;国行iPhone 暂时还不可用&#xff0c;只能等审核通过了。 1&#xff0c;Safari下载进度 过去通过S…

HBase理论_HBase架构组件介绍

近来有些空闲时间&#xff0c;正好最近也在开发HBase相关内容&#xff0c;借此整理一下学习和对HBase组件的架构的记录和个人感受&#xff0c;付出了老夫不少心血啊&#xff0c;主要介绍的就是HBase的架构设计以及我的拓展内容。内容如有不当或有其他理解 matirx70163.com HB…

【Spring AOP 原理】

首先AOP跟OOP(面向对象编程)、IOC(控制反转)一样都是一种编程思想 跟OOP不同, AOP是面向切面编程, 面对多个不具备继承关系的对象同时需要引入一段公共逻辑的时候, OOP就显得有点笨重了, 而AOP就游刃有余, 一个切面可以横跨多个类或者对象去执行公共逻辑, 极大的提升了开发效率…

第六节、Docker 方式部署指南 github 上项目 mkdocs-material

一、简介 MkDocs 可以同时编译多个 markdown 文件,形成书籍一样的文件。有多种主题供你选择,很适合项目使用。 MkDocs 是快速,简单和华丽的静态网站生成器,可以构建项目文档。文档源文件在 Markdown 编写,使用单个 YAML 配置文件配置。 MkDocs—markdown项目文档工具,…

论文 | The Capacity for Moral Self-Correction in LargeLanguage Models

概述 论文探讨了大规模语言模型是否具备“道德自我校正”的能力&#xff0c;即在收到相应指令时避免产生有害或偏见输出的能力。研究发现&#xff0c;当模型参数达到一定规模&#xff08;至少22B参数&#xff09;并经过人类反馈强化学习&#xff08;RLHF&#xff09;训练后&…

初识GIS

文章目录 一、什么叫地理信息1、定义2、主要特点3、分类 二、什么叫GIS1、定义2、GIS对空间信息的储存2.1、矢量数据模型2.2、栅格数据模型 3、离散栅格和连续栅格的区别 三、坐标系统1、为什么要存在坐标系统&#xff1f;2、地理坐标系2.1、定义与特点2.2、分类 3、投影坐标系…

通过 HTTP 获取远程摄像头视频流并使用 YOLOv5 进行目标检测

在本教程中&#xff0c;我们将通过 HTTP 获取远程摄像头视频流&#xff0c;并使用 YOLOv5 模型进行实时目标检测。我们会利用 Python 的 OpenCV 库获取视频流&#xff0c;使用 YOLOv5 模型进行目标检测&#xff0c;并使用多线程来提高实时性和效率。 项目地址&#xff1a;like…

Android Framework AMS(17)APP 异常Crash处理流程解读

该系列文章总纲链接&#xff1a;专题总纲目录 Android Framework 总纲 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;本章节主要解读APP Crash处理。关注思维导图中左上侧部分即可。 本章节主要是对Android的APP Crash处理有一个基本的了解。从进程启动到UncaughtH…

javaWeb小白项目--学生宿舍管理系统

目录 一、检查并关闭占用端口的进程 二、修改 Tomcat 的端口配置 三、重新启动 Tomcat 一、javaw.exe的作用 二、结束javaw.exe任务的影响 三、如何判断是否可以结束 结尾&#xff1a; 这个错误提示表明在本地启动 Tomcat v9.0 服务器时遇到了问题&#xff0c;原因是所需…

深度学习在边缘检测中的应用及代码分析

摘要&#xff1a; 本文深入探讨了深度学习在边缘检测领域的应用。首先介绍了边缘检测的基本概念和传统方法的局限性&#xff0c;然后详细阐述了基于深度学习的边缘检测模型&#xff0c;包括其网络结构、训练方法和优势。文中分析了不同的深度学习架构在边缘检测中的性能表现&am…

SpringBoot(十七)创建多模块Springboot项目

在gitee上查找资料的时候,发现有不少Springboot项目里边都是嵌套了多个Springboot项目的。这个玩意好,在协作开发的时候,将项目分成多个模块,有多个团队协作开发,模块间定义标准化通信接口进行数据交互即可。 这个好这个。我之前创建的博客项目是单模块的SpringBoot项目,…

STM32WB55RG开发(2)----STM32CubeProgrammer烧录

STM32WB55RG开发----2.STM32CubeProgrammer烧录 概述硬件准备视频教学样品申请源码下载参考程序自举模式UART烧录USB烧录 概述 STM32CubeProgrammer (STM32CubeProg) 是一款用于编程STM32产品的全功能多操作系统软件工具。 它提供了一个易用高效的环境&#xff0c;通过调试接口…

使用Java爬虫获取商品订单详情:从API到数据存储

在电子商务日益发展的今天&#xff0c;获取商品订单详情成为了许多开发者和数据分析师的需求。无论是为了分析用户行为&#xff0c;还是为了优化库存管理&#xff0c;订单数据的获取都是至关重要的。本文将详细介绍如何使用Java编写爬虫&#xff0c;通过API获取商品订单详情&am…