代码随想录算法训练营第六十一天| 53. 寻宝 Prim 算法、53. 寻宝 Kruskal 算法

[KamaCoder] 53. 寻宝(第七期模拟笔试)

[KamaCoder] 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

2 <= V <= 10000;
1 <= E <= 100000;
0 <= val <= 10000;

[KamaCoder] 53. 寻宝 Prim 算法(第七期模拟笔试)

自己看到题目的第一想法

    完全理解错了, 我以为是找到一条从节点 1 出发到节点 n , 路上要经过所有节点的路径最短的方案. 题目要求的是将所有城市连接在一起后, 要求建造的公路是的长度是最短的.

看完代码随想录之后的想法

    对于希望节点连接在一起后, 节点连线的权值总和是最小的. 有两种方案, 一种是 prim 算法, 一种是 kruskal 算法.

    对于 prim 算法, 就是从一个节点出发, 将该节点添加到最小生成树中, 同时更新与该节点相连的其他节点的距离. 因为节点默认是不和最小生成树相连的, 因此初始化时需要随意添加一个节点到最小生成树中, 该节点添加到进来后, 最小生成树就是该节点. 当有一个节点添加到最小生成树时, 与该节点相连的其他节点距离最小生成树的距离就是确定的了, 同时也不可能有其他更短的连接方式连接到该节点. 因此, 每次选择距离最小生成树最近的节点添加到最小生成树, 并更新完与该节点相邻的节点距离该生成树的距离后, 距离该最小生成树最近的节点也就知道了. 不停的往最小生成树中添加距离最近的节点, 直到添加 n - 1次后, 最小生成树将有 n 个节点, 所有节点就计算完成了.

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

prim 算法
public class Main {
    private static int[][] nodes = null;
    private static int[] minDist = null;
    private static boolean[] isInTree = null;
    // 记录相连的边
    // private static int[] parents = null;
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int vertexCount = 0;
        while (scanner.hasNext()) {
            vertexCount = scanner.nextInt();
            int sideCount = scanner.nextInt();
            minDist = new int[vertexCount + 1];
            nodes = new int[vertexCount + 1][vertexCount + 1];
            // 记录相连的边
            // parents = new int[vertexCount + 1];
            isInTree = new boolean[vertexCount + 1];
            for (int i = 0; i <= vertexCount; i++) {
                minDist[i] = Integer.MAX_VALUE;
            }
            int fromNode;
            int toNode;
            int val;
            for (int i = 0; i < sideCount; i++) {
                fromNode = scanner.nextInt();
                toNode = scanner.nextInt();
                val = scanner.nextInt();
                nodes[fromNode][toNode] = val;
                nodes[toNode][fromNode] = val;
            }
        }
        minDist[1] = 0;
        for (int i = 0; i < vertexCount - 1; i++) {
            int minDistIndex = 0;
            for (int j = 1; j <= vertexCount; j++) {
                if (!isInTree[j] && minDist[j] < Integer.MAX_VALUE && minDist[j] < minDist[minDistIndex]) {
                    minDistIndex = j;
                }
            }
            isInTree[minDistIndex] = true;
            for (int j = 1; j <= vertexCount; j++) {
                if (nodes[minDistIndex][j] != 0 && !isInTree[j] && nodes[minDistIndex][j] < minDist[j]) {
                    minDist[j] = nodes[minDistIndex][j];
                    // 记录相连的边
                    // parents[j] = minDistIndex;
                }
            }
        }
        int result = 0;
        for (int i = 1; i < minDist.length; i++) {
            // 记录相连的边: 打印
            // System.out.println(parents[i] + " -> " + i);
            result += minDist[i];
        }
        System.out.println(result);
    }
}

自己实现过程中遇到哪些困难 

    1. 没想到只要添加 n - 1 个节点到最小生成树即可完成到所有节点最小路径的计算. 最开始采用的是记录一下最小生成树的节点数量, 当所有节点都添加到最小生成树后, 则停止.

    2. 没有使用 boolean[] isInTree 来判断节点是否计算过, 而是使用 List<Integer> 来计算, 导致性能耗时

    3. 最严重的是, 一开始完全理解错了 prim 算法的意图, 以为是想找到一条从节点 1 出发到 节点 n, 最短的路径. 实际上找的是连接所有节点后, 路劲最短的连接方式.

[KamaCoder] 53. 寻宝(第七期模拟笔试)

[KamaCoder] 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

数据范围:
2 <= V <= 10000;
1 <= E <= 100000;
0 <= val <= 10000;

[KamaCoder] 53. 寻宝 Kruskal 算法(第七期模拟笔试)

自己看到题目的第一想法

    无

看完代码随想录之后的想法

    感觉 prim 和 kruskal 都让我有一点点似懂非懂的感觉. 特别是 kruskal 算法, 每次挑选权重更小的边, 如果产生了环路, 说明当前节点已经相连, 不需要继续添加. 非常合理, 又非常得过于符合直觉... 让我产生了一种, 不知道自己是不是真的懂了的感觉... 一般就是真的没有完全懂. 头疼.

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

// 
public class Main {
    // ====> 并查集部分 开始 <====
    private static int[] father = null;
    // ====> 并查集部分 结束 <====
    
    // ====> Kruskal 部分 开始 <====
    private static class Edge {
        int from;
        int to;
        int val;
    }
    private static List<Edge> edges = new ArrayList<>();
    // ====> Kruskal 部分 结束 <====
    
    // 记录相连的边
    // private static int[] parents = null;
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            init(scanner.nextInt());
            int sideCount = scanner.nextInt();

            // 记录相连的边
            // parents = new int[vertexCount + 1];
            for (int i = 0; i < sideCount; i++) {
                Edge edge = new Edge();
                edge.from = scanner.nextInt();
                edge.to = scanner.nextInt();
                edge.val = scanner.nextInt();
                edges.add(edge);
            }
        }
        Collections.sort(edges, new Comparator<Edge>() {
            @Override
            public int compare(Edge edge1, Edge edge2) {
                return edge1.val - edge2.val;
            }
        });
        int result = 0;
        for (Edge edge : edges) {
            if (isSame(edge.from, edge.to)) {
                continue;
            }
            join(edge.from, edge.to);
            result += edge.val;
        }
        System.out.println(result);
    }
    
    // ====> 并查集部分 开始 <====
    private static void init(int nodeCount) {
        father = new int[nodeCount + 1];
        for (int i = 0; i <= nodeCount; i++) {
            father[i] = i;
        }
    }
    
    private static boolean isSame(int u, int v) {
        return find(u) == find(v);
    }
    
    private static int find(int u) {
        if (u == father[u]) {
            return u;
        }
        return father[u] = find(father[u]);
    }
    
    private static void join(int u, int v) {
        u = find(u);
        v = find(v);
        if (u == v) {
            return;
        }
        father[v] = u;
    }
    // ====> 并查集部分 结束 <====
}

自己实现过程中遇到哪些困难 

    无, Kruskal 算法在测试用例里性能是高一些的, 取决于排序算法吧.

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值