一个有 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;
}
}; 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;
}
}; 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 /**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 返回最小的花费代价使得这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]++;
}
}
}