一个有 n 户人家的村庄,有 m 条路相互连接着。村里现在要修路,每条路都有一个成本价格,现在请你帮忙计算下,最少需要花费多少钱,就能让这 n 户人家连接起来。
为一个二维数组,每个元素是一个长度为 3 的一维数组 , 和 表示村庄 和村庄 有一条路,修这条路的成本价格为 。
每户之间可能有多条道路连接,但不可能自己与自己相连。
数据范围: , ,
进阶: 时间复杂度 , 空间复杂度
3,3,[[1,3,3],[1,2,1],[2,3,1]]
2
2,1,[[1,2,1]]
1
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
/** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 返回最小的花费代价使得这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; }
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 { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 返回最小的花费代价使得这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; } }
/** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 返回最小的花费代价使得这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; } };
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; } }
/** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 返回最小的花费代价使得这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 Find(int *A, int i){ if(A[i]==i) return i; else return A[i] = Find(A, A[i]); } void Union(int *A, int i, int j){ A[Find(A, j)] = A[Find(A, i)]; } void SetArr3(int *dest, int* src){ dest[0] = src[0]; dest[1] = src[1]; dest[2] = src[2]; } void ShellSort(int **cost, int lenc){ int nextc[3], j; for(int m = lenc/2;m>=1;m/=2){ for(int i=m;i<lenc;i++){ SetArr3(nextc, cost[i]); for(j=i; (cost[j-m][2]>nextc[2])&&(j-m>=0);j-=m){ SetArr3(cost[j], cost[j-m]); } SetArr3(cost[j], nextc); } } } int miniSpanningTree(int n, int m, int** cost, int costRowLen, int* costColLen ) { // shellsort O(mlogm) ShellSort(cost, m); // 并查集 O(n), 查询接近O(1) int arr[n]; int sumcost = 0; for(int i=0;i<n;i++) arr[i] = i; // kruskal O(m) for(int i=0;i<m;i++){ if(Find(arr, cost[i][1]-1)!=Find(arr, cost[i][0]-1)){ Union(arr, cost[i][1]-1, cost[i][0]-1); sumcost += cost[i][2]; } } 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整型 */ class UnionSet { private int nodeNum; private int[] parent; UnionSet(int num) { this.nodeNum = num; parent = new int[nodeNum]; for(int i = 0; i < nodeNum; i++) { parent[i] = i; } } public void union(int i, int j) { parent[find(i)] = find(j); } public int find(int i) { if(parent[i] != i) { parent[i] = find(parent[i]); } return parent[i]; } } public int miniSpanningTree (int n, int m, int[][] cost) { // write code here // 并查集 Arrays.sort(cost, (a, b) -> { return a[2] - b[2]; }); UnionSet unionSet = new UnionSet(n + 1); int res = 0; for(int []list: cost) { if(unionSet.find(list[0]) != unionSet.find(list[1])) { unionSet.union(list[0], list[1]); res += list[2]; } } return res; } }
#include <algorithm> #include <vector> class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 返回最小的花费代价使得这n户人家连接起来 * @param n int整型 n户人家的村庄 * @param m int整型 m条路 * @param cost int整型vector<vector<>> 一维3个参数,表示连接1个村庄到另外1个村庄的花费的代价 * @return int整型 */ vector<vector<int>> uset; int find(int u){ if(u==uset[u][0]) return u; return find(uset[u][0]); } void unio(int u, int v){ int rootu = find(u); int rootv = find(v); if(rootu!=rootv){ if(uset[rootu][1]>uset[rootv][1]){ uset[rootv][0] = rootu; }else if(uset[rootu][1]<uset[rootv][1]){ uset[rootu][0] = rootv; }else{ uset[rootu][0] = rootv; uset[rootu][1]++; } } } static bool cmp(vector<int> &a, vector<int> &b){ return a[2]<b[2]; } int miniSpanningTree(int n, int m, vector<vector<int> >& cost) { // write code here uset = vector<vector<int>>(n+1,vector<int>(2)); sort(cost.begin(),cost.end(), cmp); int ans = 0; for(int i=1; i<=n; i++){ uset[i][0] = i; uset[i][1] = 1; } for(int i=0; i<m; i++){ int u = cost[i][0], v = cost[i][1]; if(find(u)!=find(v)){ ans += cost[i][2]; unio(u,v); } } return ans; } };
# # 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 # # 返回最小的花费代价使得这n户人家连接起来 # @param n int n户人家的村庄 # @param m int m条路 # @param cost int二维数组 一维3个参数,表示连接1个村庄到另外1个村庄的花费的代价 # @return int # class UnionFind: def __init__(self,n): self.parent=list(range(n)) self.partN=n def find(self,x): if self.parent[x]!=x: self.parent[x]=self.find(self.parent[x]) return self.parent[x] def union(self,x,y): Ix=self.find(x) Iy=self.find(y) if Ix!=Iy: self.parent[Ix]=Iy self.partN-=1 return True return False class Solution: def miniSpanningTree(self , n , m , cost ): cost.sort(key=lambda x:x[2]) ans=0 uf=UnionFind(n) for x in cost: if uf.partN!=1: if uf.union(x[0]-1,x[1]-1): ans+=x[2] else: break return ans
为什么prim算法通过不了,只能过前六个测试,不知道问题出哪里了 public int miniSpanningTree(int n, int m, int[][] cost) { int[][] graph = new int[n][n]; //构建邻接矩阵图 for (int i = 0; i < m; i++) { int u = cost[i][0] - 1; int v = cost[i][1] - 1; int weight = cost[i][2]; graph[u][v] = weight; graph[v][u] = weight; } if (n == 2) return cost[0][2]; // write code here //status数组记录节点是否联通了,初始化都为false boolean[] status = new boolean[n]; Arrays.fill(status, false); //distance数组记录各个点到联通部分的最短距离,初始化无穷 int[] dist = new int[n]; Arrays.fill(dist, Integer.MAX_VALUE); //pre数组记录节点和谁联通,初始化都为-1 int[] pre = new int[n]; Arrays.fill(pre, -1); //开始遍历,n个点需要遍历n-1次,从点0(下标)开始 dist[0] = 0; int totalCost = 0; for (int i = 0; i < n; i++) {//循环次数为点的个数,因为每个点都要加入mst // 选择键值最小的顶点u,它尚未包含在MST中 int u = -1;//初始化该点为不连通(可能没必要?) int max = Integer.MAX_VALUE;//初始化一个值为整型最大值来循环排除无穷不可达 for (int j = 0; j < n; j++) { if (!status[j] && dist[j] < max) { max = dist[j];//这里不能忘记更新 u = j; } } //找到相应点,添加到集合 if (u == -1) break; status[u] = true; totalCost += dist[u]; for (int v = 0; v < n; v++) { if (!status[v] && graph[u][v] != 0 && graph[u][v] < dist[v]) { pre[v] = u; dist[v] = graph[u][v]; } } } return totalCost; }