[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. 寻宝(第七期模拟笔试) 文章解释
题目描述
在世界的某个区域,有一些分散的神秘岛屿,每个岛屿上都有一种珍稀的资源或者宝藏。国王打算在这些岛屿上建公路,方便运输。
不同岛屿之间,路途距离不同,国王希望你可以规划建公路的方案,如何可以以最短的总公路距离将 所有岛屿联通起来(注意:这是一个无向图)。
给定一张地图,其中包括了所有的岛屿,以及它们之间的距离。以最小化公路建设长度,确保可以链接到所有岛屿。
输入描述
第一行包含两个整数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 算法在测试用例里性能是高一些的, 取决于排序算法吧.