一个有 n 户人家的村庄,有 m 条路相互连接着。村里现在要修路,每条路都有一个成本价格,现在请你帮忙计算下,最少需要花费多少钱,就能让这 n 户人家连接起来。
每户之间可能有多条道路连接,但不可能自己与自己相连。
数据范围:
,
,
进阶: 时间复杂度
, 空间复杂度 )
3,3,[[1,3,3],[1,2,1],[2,3,1]]
2
2,1,[[1,2,1]]
1
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 返回最小的花费代价使得这n户人家连接起来 * @param n int n户人家的村庄 * @param m int m条路 * @param cost int二维数组 一维3个参数,表示连接1个村庄到另外1个村庄的花费的代价 * @return int */ public int miniSpanningTree (int n, int m, int[][] cost) { // write code here if(n < 1 || m < 1 || cost == null) return 0; //参考地址:https://blog.csdn.net/qq_41754350/article/details/81460643 //基本思想: 以边为主导地位,始终选择当前可用的最小边权的边(sort); //每次选择边权最小的边连接两个端点是kruskal的规则,并实时判断两个点之间有没有间接联通; Arrays.sort(cost, new Comparator<int[]>(){ public int compare(int[] a, int[] b){ return a[2] - b[2];//根据权值升序排列 } }); int minPath = 0; MinTree mt = new MinTree(n + 1);//节点从1开始 for(int[] c : cost){ if(mt.find(c[0]) != mt.find(c[1])){//没有直接或间接联通 mt.union(c[0], c[1]); minPath += c[2]; } } return minPath; } } //最小生成树,借助并查集的知识 class MinTree{ private int[] father;//属性,根 public MinTree(int n){//构造器 father = new int[n]; for(int i = 0; i < n; i++){ father[i] = i; } } //将x合并到y public void union(int x, int y){ int xroot = find(x); int yroot = find(y); if(xroot == yroot) return; father[xroot] = yroot; } //查找根, 同时压缩路径 public int find(int x){ if(father[x] != x){ father[x] = find(father[x]); } return father[x]; } }
卡鲁斯卡尔 求最小生成树 class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 返回最小的花费代价使得这n户人家连接起来 * @param n int n户人家的村庄 * @param m int m条路 * @param cost intvector<vector<>> 一维3个参数,表示连接1个村庄到另外1个村庄的花费的代价 * @return int */ int find(int x)//并查集合并及查找根节点 { if(x !=p[x]) p[x] = find(p[x]); return p[x]; } int p[100010]; int miniSpanningTree(int n, int m, vector<vector<int> >& cost) { // write code here for(int i = 0; i <= n; i++) p[i] = i;//初始化并查集 //排序 sort(cost.begin(),cost.begin() + m, [](vector<int>& a, vector<int> &b){return a[2] < b[2];}); int res = 0; for(int i = 0; i < m; i++) { if(find(cost[i][0]) != find(cost[i][1]))//如果不是同一个集合, { res += cost[i][2];//加路 p[find(cost[i][0])] = find(cost[i][1]);//合并集合 } } return res; } };
import java.util.*; public class Solution { int[] parent; int findParent(int i) { if(parent[i] == i) return i; parent[i] = findParent(parent[i]); return parent[i]; } public int miniSpanningTree (int n, int m, int[][] cost) { // write code here parent = new int[n + 1]; for(int i = 0; i <= n; i ++) { parent[i] = i; } Arrays.sort(cost, new Comparator<int[]>(){ @Override public int compare(int[] p1,int[] p2) { return p1[2] - p2[2]; } }); int w = 0; for(int i = 0; i < m; i ++) { int a = cost[i][0]; int b = cost[i][1]; int p1 = findParent(a); int p2 = findParent(b); if(p1 != p2) { w = w + cost[i][2]; parent[p2] = p1; } } return w; } }
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 返回最小的花费代价使得这n户人家连接起来 * @param n int n户人家的村庄 * @param m int m条路 * @param cost int二维数组 一维3个参数,表示连接1个村庄到另外1个村庄的花费的代价 * @return int */ public int miniSpanningTree (int n, int m, int[][] cost) { // write code here Arrays.sort(cost, (a, b) -> a[2] - b[2]); // 先按边升序排列 UnionFind uf = new UnionFind(n); int money = 0; for(int i = 0; i < m; i++){ int from = cost[i][0] - 1; int to = cost[i][1] - 1; int costMoney = cost[i][2]; if(!uf.isConnected(from, to)){ uf.union(from, to); money += costMoney; } if(uf.getCount() == 1){ // 所有节点已经连为一体了,退出循环 break; } } return money; } } class UnionFind { private int[] parent; // 每棵树的根节点 private int[] rank; // 每棵树的相对高度 private int count; // 连通分量数 public UnionFind(int n) { parent = new int[n]; rank = new int[n]; for(int i = 0; i < n; i++){ parent[i] = i; rank[i] = 1; } } public int find(int x) { while(x != parent[x]){ parent[x] = parent[parent[x]]; x = parent[x]; } return x; } public void union(int x, int y) { int rootX = find(x); int rootY = find(y); // 两个节点根不相同时进行合并操作 if(rootX != rootY){ if(rank[rootX] > rank[rootY]){ // x的树比y高,将y合并到x下 parent[rootY] = rootX; }else if(rank[rootX] < rank[rootY]){ // x的树比y矮,将x合并到y下 parent[rootX] = rootY; }else{ // 树一样高时合并顺序随意,但是合并另一个子树的树高度要改变 parent[rootX] = rootY; rank[rootY] ++; } } count--; // 缩减一个分量 } // 判断两个节点的连通性 public boolean isConnected(int x, int y) { return find(x) == find(y); // 比较根节点是否相同即可 } // 返回连通分量数 public int getCount() { return count; } }
class Solution: def miniSpanningTree(self , n , m , cost ): cost=sorted(cost, key=lambda x: x[2] ) ## tag 即为顶点的标记字典,可以通过顶点查询它的标记 tag={ vtx : vtx for vtx in range(1,n+1) } result=[] sum=0 for u,v,w in cost: if (tag[u]!=tag[v]): ## u,v are not connected, choose this edge for construction result.append( [u,v,w] ) temp=tag[v] for vtx in range(1,n+1): if tag[vtx]==temp: tag[vtx]=tag[u] sum+=w if(len(result)==n-1): return sum return sum
class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 返回最小的花费代价使得这n户人家连接起来 * @param n int整型 n户人家的村庄 * @param m int整型 m条路 * @param cost int整型vector<vector<>> 一维3个参数,表示连接1个村庄到另外1个村庄的花费的代价 * @return int整型 */ // Prim算法生成最小树 int miniSpanningTree(int n, int m, vector<vector<int>>& cost) { int a[5001][5001] = {0}; bool isDetected[5001] = {false}; int distance[5001] = {0}; // 到最小生成树的距离 long long minDistance = 0; if (n == 1) return 0; for (int i = 0; i < m; i++) { if (a[cost[i][0]][cost[i][1]] == 0 || a[cost[i][0]][cost[i][1]] > cost[i][2]) { a[cost[i][0]][cost[i][1]] = cost[i][2]; a[cost[i][1]][cost[i][0]] = cost[i][2]; } } int searchPoint = 1; for (int j = 0; j < n - 1; j++) { isDetected[searchPoint] = true; for (int i = 1; i <= n; i++) { if (!isDetected[i]) { // 计算距离searchPoint的dist if (a[searchPoint][i] > 0 && (a[searchPoint][i] < distance[i] || distance[i] == 0)) { distance[i] = a[searchPoint][i]; } } } // 选取最小dist int tempMinDis = 10001; for (int i = 1; i <= n; i++) { if (tempMinDis > distance[i] && distance[i] > 0 && !isDetected[i]) { tempMinDis = distance[i]; searchPoint = i; } } minDistance += tempMinDis; } return minDistance; } };
/** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 返回最小的花费代价使得这n户人家连接起来 * @param n int整型 n户人家的村庄 * @param m int整型 m条路 * @param cost int整型二维数组 一维3个参数,表示连接1个村庄到另外1个村庄的花费的代价 * @param costRowLen int cost数组行数 * @param costColLen int* cost数组列数 * @return int整型 */ int miniSpanningTree(int n, int m, int** cost, int costRowLen, int* costColLen) { // 定义一个数组来存储每个顶点到已加入生成树的最近顶点的距离 int homevexcost[n]; // 定义一个邻接矩阵来存储各顶点之间的边的权重 int edgecost[n][n]; // 初始化最小生成树的总成本为0 int sumcost = 0; int u = 0; int v = 0; int w = 0; // 初始化邻接矩阵中未连接的顶点间的权重为极大值(这里用32767表示) for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { edgecost[i][j] = 32765; } } // 根据给定的边和成本信息填充邻接矩阵 for (int i = 0; i < costRowLen; i++) { u = cost[i][0] - 1; v = cost[i][1] - 1; w = cost[i][2]; edgecost[u][v] = w; edgecost[v][u] = w; // 因为图是无向的 } // 初始化第一个顶点加入生成树的成本为0 homevexcost[0] = 0; // 计算从第一个顶点出发到其他顶点的最短距离 int k = 0; for (int i = 0; i < n; i++) { homevexcost[i] = edgecost[k][i]; } // 主循环来构建最小生成树 for (int i = 1; i < n; i++) { // 寻找离已加入生成树的顶点集合最近的顶点及其成本 int mincost = 32765; int l; for (int j = 0; j < n; j++) { if (homevexcost[j] != 0 && homevexcost[j] < mincost) { mincost = homevexcost[j]; l = j; } } // 更新已加入生成树的顶点集合,并累加当前边的成本到总成本中 k = l; sumcost += mincost; homevexcost[l] = 0; // 更新从新加入的顶点出发到其他顶点的距离 for (int a = 0; a < n; a++) { if (edgecost[k][a] < homevexcost[a]) { homevexcost[a] = edgecost[k][a]; } } } // 返回最小生成树的总成本 return sumcost; }
import java.util.*; public class Solution { /** * 返回最小的花费代价使得这n户人家连接起来 * @param n int整型 n户人家的村庄 * @param m int整型 m条路 * @param cost int整型二维数组 一维3个参数,表示连接1个村庄到另外1个村庄的花费的代价 * @return int整型 */ public int miniSpanningTree(int n, int m, int[][] cost) { Arrays.sort(cost, Comparator.comparingInt(a -> a[2])); // 根据花费从小到大排序 UnionFind uf = new UnionFind(n + 1); // 创建并查集,注意顶点从1开始,所以大小为 n+1 int minCost = 0; int edgesUsed = 0; for (int[] edge : cost) { int u = edge[0]; int v = edge[1]; int weight = edge[2]; if (!uf.connected(u, v)) { // 如果u和v不在同一个集合 uf.union(u, v); // 合并u和v所在的集合 minCost += weight; // 累加成本 edgesUsed++; // 使用的边数增加 if (edgesUsed == n - 1) break; // 如果已经使用了 n-1 条边,构成了最小生成树 } } // 如果使用的边数不足 n-1,说明无法连接所有村庄 return edgesUsed == n - 1 ? minCost : -1; } // 并查集类 static class UnionFind { private final int[] parent; private final int[] rank; public UnionFind(int size) { parent = new int[size]; rank = new int[size]; for (int i = 0; i < size; i++) { parent[i] = i; rank[i] = 0; } } public int find(int p) { if (p != parent[p]) { parent[p] = find(parent[p]); // 路径压缩 } return parent[p]; } public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP != rootQ) { if (rank[rootP] > rank[rootQ]) { parent[rootQ] = rootP; } else if (rank[rootP] < rank[rootQ]) { parent[rootP] = rootQ; } else { parent[rootQ] = rootP; rank[rootP]++; } } } public boolean connected(int p, int q) { return find(p) == find(q); } } }
class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 返回最小的花费代价使得这n户人家连接起来 * @param n int n户人家的村庄 * @param m int m条路 * @param cost intvector<vector<>> 一维3个参数,表示连接1个村庄到另外1个村庄的花费的代价 * @return int */ int my_union[5505]; int find_root(int x) { if(my_union[x] != x) { return find_root(my_union[x]); } return x; } int miniSpanningTree(int n, int m, vector<vector<int> >& cost) { int count=0; int aa,bb; int w=0; for(int i=0;i<n;i++) my_union[i+1]=i+1; sort(cost.begin(),cost.end(),[](vector<int> &a,vector<int> &b){return a[2] < b[2];}); for(int i=0;i<m;i++) { aa=find_root(cost[i][0]); bb=find_root(cost[i][1]); if( aa!=bb ) { my_union[bb]=aa; w=w+cost[i][2]; count++; } if(count==n-1) break; } return w; } };
class Solution { public: int miniSpanningTree(int n, int m, vector<vector<int> >& cost) { //使用Prim算法,从一个根节点来逐渐让树“长大”。 //加入新节点时是从未加入树中的节点中搜索,因此不用担心构成回路。 //记录这个节点的父节点是谁。同时也起到了标志该节点有没有被访问过的作用。 vector<int> parent(n); vector<bool> flag(n); //记录这个点离S集,或者说离树的距离。只有与树的节点直接相邻的节点,才算可达。 vector<int> dist(n); //缓存此轮循环中被加入的顶点的下标,视作为下一次循环时的父节点。 int lastparent=0; //对所有节点初始化。 for(int i=0;i<n;i++) { parent[i]=-1; dist[i]=INT_MAX; flag[i]=false; } dist[0]=0; flag[0]=true; //初始化树根的所有邻接点。 for(auto edge:cost) { if( (edge[0]-1) ==0) { //将它们的父节点设置为树根 parent[edge[1]-1]=0; dist[edge[1]-1]=edge[2]; } } int sum=0; while(1) { int mindistance=INT_MAX; int minindex=-1; //寻找出距离“树”最近的节点。根节点可以无视。 for(int i=1;i<parent.size();i++) { //如果还没被访问过 //if(parent[i]==-1) if(flag[i]==false) { if(dist[i]<mindistance) { mindistance=dist[i]; minindex=i; } } } //如果所有的节点都被加入到树中了。 if(minindex==-1) { break; } flag[minindex]=true; //将找到的节点的所有邻接点设为可达,即将它们的边值更新进dist数组。 for(auto edge:cost) { //如果没被访问过 if( (edge[0]-1) ==minindex && flag[edge[1]-1]==false) { //它的邻接点的下标。 int adjoinIndex=edge[1]-1; if(edge[2]<=dist[adjoinIndex]) { dist[adjoinIndex]=edge[2]; parent[adjoinIndex]=minindex; } } } sum+=dist[minindex]; } //构建树结束,现在遍历dist数组。来计算总的代价 return sum; } };
/** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 返回最小的花费代价使得这n户人家连接起来 * @param n int n户人家的村庄 * @param m int m条路 * @param cost int二维数组 一维3个参数,表示连接1个村庄到另外1个村庄的花费的代价 * @param costRowLen int cost数组行数 * @param costColLen int* cost数组列数 * @return int //only return one value */ typedef struct { int adjvex; int lowcost; }close_edge; int minimum(close_edge* closedge, int n) { int i = 0; int index = -1; int min = 9000000; for (i = 1; i <= n; ++i) if (closedge[i].lowcost && min > closedge[i].lowcost) { min = closedge[i].lowcost; index = i; } return index; } int miniSpanningTree(int n, int m, int** cost, int costRowLen, int* costColLen ) { // write code here int* vexs = (int*)calloc(n + 1, sizeof(int)); int i = 0; int j = 0; for (i = 1; i <= n; ++i) vexs[i] = i; int** Matrix = (int**)calloc(n + 1, sizeof(int *)); for (i = 0; i < n + 1; ++i) { int* row = (int *)calloc(n + 1, sizeof(int)); Matrix[i] = row; } for (i = 0; i < n + 1; ++i) for (j = 0; j < n + 1; ++j) Matrix[i][j] = 800000; for (i = 0; i < m; ++i) Matrix[cost[i][0]][cost[i][1]] = Matrix[cost[i][1]][cost[i][0]] = cost[i][2]; close_edge* closedge = (close_edge *)calloc(n + 1, sizeof(close_edge)); closedge[0].lowcost = 0; closedge[1].lowcost = 0; for (i = 2; i <= n; ++i) { closedge[i].adjvex = 1; closedge[i].lowcost = Matrix[1][i]; } int count = 0; int k = -1; for (i = 1; i < n; ++i) { k = minimum(closedge, n); count += closedge[k].lowcost; closedge[k].lowcost = 0; for (j = 1; j <= n; ++j) if (Matrix[k][j] < closedge[j].lowcost) { closedge[j].adjvex = vexs[k]; closedge[j].lowcost = Matrix[k][j]; } } return count; }
欸,不是不会自己和自己相连吗,那为什么
for(int i=0;i<cost.size();i++){ if(cost[i][0]==cost[i][1]){ std::cout<<"输入了自环!!!:"<<cost[i][0]<<",value:"<<cost[i][2]<<std::endl; } }
第七个用例输出:
输入了自环!!!:39,value:9034 输入了自环!!!:62,value:206 输入了自环!!!:27,value:6412 输入了自环!!!:80,value:9944 输入了自环!!!:9,value:4172 输入了自环!!!:91,value:9312 输入了自环!!!:34,value:349 输入了自环!!!:32,value:1670 输入了自环!!!:71,value:4854 输入了自环!!!:42,value:7700 输入了自环!!!:57,value:1958
import java.util.*; public class Solution { public int miniSpanningTree (int n, int m, int[][] cost) { // return Kruskal(n, m, cost); return Prim(n, m, cost); } //【2】Prim int Prim(int n, int m, int[][] cost) { // 每次选择不在生成树节点集合 的最短边 int[][] g = new int[n + 1][n + 1]; // 邻接矩阵 for(int i = 1; i <= n; i++) Arrays.fill(g[i], Integer.MAX_VALUE); for (int i = 0; i < m; i++) { int u = cost[i][0], v = cost[i][1], w = cost[i][2]; // if(u == v) continue; // 自环 if (w < g[u][v]) { // 可能有重复边, 取小的 【注意】 g[u][v] = w; g[v][u] = w; } } boolean[] used = new boolean[n+1]; PriorityQueue<int[]> queue = new PriorityQueue<>((a, b) -> a[1] - b[1]); // 小根堆 queue.offer(new int[] {1, 0}); int res = 0, node = n; // 选n个点 【注意】(不加node判断会超时) while (!queue.isEmpty()&& node > 0) { int[] a = queue.poll(); int i = a[0]; if (used[i]) continue; res += a[1]; used[i] = true; // 节点i加入生成树 node--; for (int j = 1; j <= n; j++) { if (!used[j]) { queue.offer(new int[] {j, g[i][j]}); // queue自动poll最小边,只管加入就行 } } } return res; } //【1】 Kruskal(并查集) int Kruskal(int n, int m, int[][] cost) { // 按边权重 升序, 每次选最小的边 且不会形成环路 UnionFind uf = new UnionFind(n); Arrays.sort(cost, (a, b)->a[2] - b[2]); int edge = n - 1, res = 0, i = 0; while (edge-- > 0) { // 选n-1条边 while (uf.find(cost[i][0]) == uf.find(cost[i][1])) i++; res += cost[i][2]; uf.union(cost[i][0], cost[i][1]); // 合并两点 } return res; } } //🍓 并查集 (+ 路径压缩、按秩合并) class UnionFind { private int[] parent; // 父节点数组 private int[] rank; // 秩数组(树高度) // 初始化:父节点指向自己, 秩置1 public UnionFind(int n) { parent = new int[n + 1]; rank = new int[n + 1]; for (int i = 0; i <= n; i++) { parent[i] = i; rank[i] = 1; } } // 查找 (路径压缩) public int find(int x) { if (parent[x] != x) { parent[x] = find(parent[x]); } return parent[x]; } // 合并 (按秩合并) public void union(int x, int y) { int rootX = find(x), rootY = find(y); if (rootX == rootY) return; if (rank[rootX] > rank[rootY]) { // 小秩合并到大秩 parent[rootY] = rootX; } else if (rank[rootX] < rank[rootY]) { parent[rootX] = rootY; } else { // 秩相同 合并后+1 parent[rootY] = rootX; rank[rootX]++; } } }