首页 > 试题广场 >

最小生成树

[编程题]最小生成树
  • 热度指数:16605 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
一个有 n 户人家的村庄,有 m 条路相互连接着。村里现在要修路,每条路都有一个成本价格,现在请你帮忙计算下,最少需要花费多少钱,就能让这 n 户人家连接起来。

为一个二维数组,每个元素是一个长度为 3 的一维数组 表示村庄 和村庄 有一条路,修这条路的成本价格为  

每户之间可能有多条道路连接,但不可能自己与自己相连

数据范围:  ,  , 
进阶: 时间复杂度 , 空间复杂度
示例1

输入

3,3,[[1,3,3],[1,2,1],[2,3,1]]

输出

2
示例2

输入

2,1,[[1,2,1]]

输出

1
为什么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;
    }

发表于 2023-11-29 11:48:22 回复(0)
//为什么只有37%的通过率

importjava.util.*;
 
 
publicclassSolution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 返回最小的花费代价使得这n户人家连接起来
     * @param n int n户人家的村庄
     * @param m int m条路
     * @param cost int二维数组 一维3个参数,表示连接1个村庄到另外1个村庄的花费的代价
     * @return int
     */
    publicintminiSpanningTree (intn, intm, int[][] cost) {
        // write code here
        int[][] graph = newint[n + 1][n + 1];
        intresult = 0;
        for(inti = 1; i <= n; i++){
            for(intj = 1; j <= n; j++){
                graph[i][j] = Integer.MAX_VALUE;
            }
        }
        for(inti = 0; i < m; i++){
            graph[cost[i][0]][cost[i][1]] = cost[i][2];
        }
        intk = 1;
        CloseEdge[] closeEdge = newCloseEdge[n + 1];
        closeEdge[1] = newCloseEdge(-1, 0);
        for(inti = 1; i <= n; i++){
            if(i != k){
                closeEdge[i] = newCloseEdge(k, graph[k][i]);
            }
        }
        for(inti = 1; i < n; i++){
            k = minPrim(closeEdge);
            result += closeEdge[k].lowcost;
            closeEdge[k].adjvex = -1;
            closeEdge[k].lowcost = 0;
            for(intj = 1; j <= n; j++){
                if(closeEdge[j].adjvex != -1&& graph[k][j] < closeEdge[j].lowcost){
                    closeEdge[j].adjvex = k;
                    closeEdge[j].lowcost = graph[k][j];
                }
            }
        }
        returnresult;
    }
 
    publicintminPrim(CloseEdge[] closeEdge){
        intt = 1;
        while(closeEdge[t].adjvex == -1)
            t++;
        for(inti = 1; i < closeEdge.length; i++){
            if(closeEdge[i].adjvex != -1&& closeEdge[i].lowcost < closeEdge[t].lowcost)
                t = i;
        }
        returnt;
    }
 
}
 
classCloseEdge{
    intadjvex;
    intlowcost;
 
    publicCloseEdge(intadjvex, intlowcost){
        this.adjvex = adjvex;
        this.lowcost = lowcost;
    }
}
发表于 2022-11-09 17:08:25 回复(0)
import java.util.*;
public class Solution {
    
    public class bb{
        int from;
        int to;
        int w;
        public bb(int from,int to,int w){
            this.from=from;
            this.to=to;
            this.w=w;
        }
    }
    public int miniSpanningTree (int n, int m, int[][] cost) {
        // write code here
        HashSet<Integer> set=new HashSet<>();
        HashMap<Integer,List<bb>> map=new HashMap<>();
        for(int i=0;i<n;i++){
            List<bb> l=new ArrayList<>();
            map.put(i+1,l);
        }
        PriorityQueue<bb> q=new  PriorityQueue<>(new Comparator<bb>() {
            public int compare(bb o1, bb o2) {
                bb b1=(bb)o1;
                bb b2=(bb)o2;
                return b1.w-b2.w;
            }
        });
        for(int i=0;i<m;i++){
            bb a=new bb(cost[i][0],cost[i][1],cost[i][2]);
            map.get(cost[i][0]).add(a);
            map.get(cost[i][1]).add(a);
        }
        int sum=0;
        set.add(1);
        for(bb b:map.get(1)){
            q.add(b);
        }
        while(!q.isEmpty()){
            bb b=q.poll();
            if((!set.contains(b.from))||(!set.contains(b.to))){
                int d=set.contains(b.from)?b.to:b.from;
                sum+=b.w;
                set.add(d);
                for(bb B:map.get(d)){
                    q.add(B);
                }
            }
        }
        return sum;
    }
}

发表于 2022-07-18 20:27:55 回复(0)

克鲁斯卡 + 并查集

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) {
        int minCost = 0;
        UnionFind uf = new UnionFind(n);
        List<int[]> list = new ArrayList<>();
        for (int[] elem : cost) {
            list.add(elem);
        }
        list.sort((o1,o2)->o1[2]-o2[2]);

        for (int[] elem : list) {
            if (uf.find(elem[0]) != uf.find(elem[1])) {
                uf.merge(elem[0],elem[1]);
                minCost+=elem[2];
            }
        }

        return minCost;
    }

    class UnionFind {

        private int N;
        private int[] innerArr;

        public UnionFind(int n) {
            N = n;
            innerArr = new int[N+1];
            init(n);
        }

        /**
         * 并查集初始化
         * 一开始每个节点的父节点都是自己
         * @param n
         */
        private void init(int n) {
            for (int i = 1; i <=N ; i++) {
                innerArr[i]=i;
            }
        }

        /**
         * 查找父节点
         * @param num
         * @return
         */
        public int find(int num) {
            if (innerArr[num]==num) return num;
            //// 方法1:回溯寻找父节点
            //return find(innerArr[num]);

            // 方法2:在递归找寻的时候更新父节点
            // 即让最底层节点直指最顶父节点
            int topFather = find(innerArr[num]);
            innerArr[num] = topFather;
            return topFather;
        }

        /**
         * 合并两个节点所在的集合
         * 即将 num2 的父节点修改为 num1 的父节点
         * @param num1
         * @param num2
         */
        public void merge(int num1,int num2) {
            innerArr[find(num2)] = find(num1);
        }

    }
}
发表于 2022-04-10 20:24:41 回复(0)
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;
    }

}

发表于 2022-03-23 10:59:00 回复(0)
用并查集实现kruskal算法,并查集使用路径压缩和rank排序技巧进行加速
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;
    }
}

发表于 2021-12-12 09:27:10 回复(0)
import java.util.*;
//六刷终于搞懂了:克鲁斯卡尔+并查集
//https://www.bilibili.com/video/BV1g5411c7iS?from=search&seid=5350587815569640429
//https://zhuanlan.zhihu.com/p/93647900
//https://www.bilibili.com/video/BV1Mf4y117bZ?from=search&seid=914796745468480478


//整体思路知道,但是实在没搞清楚find和join的内部实现逻辑

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
        //声明和初始化并查集
        int[] fa = new int[n+1];
        for (int i = 1; i <= n; ++i)fa[i] = i;
        
        int ans = 0;
        //几户人家,几!=0,所以从1开始
        Arrays.sort(cost, (int[] a, int[] b)-> a[2]-b[2]);
        for(int i = 0; i < cost.length; i++) {
            if(find(fa, cost[i][0]) != find(fa, cost[i][1])) {
                // add edge
                join(fa, cost[i][0], cost[i][1]);
                ans += cost[i][2];
            }
        }
        return ans;
    }
    public int find(int[] fa, int x) {
        if(fa[x] != x) {
            fa[x] = find(fa, fa[x]);
        }
        return fa[x];
    }
    public void join(int[] fa, int x, int y) {
        int fax = find(fa, x);
        int fay = find(fa, y);
        fa[fax] = fay;
    }
}

发表于 2021-10-01 22:37:52 回复(0)
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
        int[] fa = new int[n+1];
        int ans = 0;
        for(int i = 0; i < fa.length; i++) fa[i] = i;
        Arrays.sort(cost, (int[] a, int[] b)-> a[2]-b[2]);
        for(int i = 0; i < cost.length; i++) {
            if(find(fa, cost[i][0]) != find(fa, cost[i][1])) {
                // add edge
                join(fa, cost[i][0], cost[i][1]);
                ans += cost[i][2];
            }
        }
        return ans;
    }
    public int find(int[] fa, int x) {
        if(fa[x] != x) {
            fa[x] = find(fa, fa[x]);
        }
        return fa[x];
    }
    public void join(int[] fa, int x, int y) {
        int fax = find(fa, x);
        int fay = find(fa, y);
        fa[fax] = fay;
    }
}


发表于 2021-07-21 16:52:36 回复(0)
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
        Graph graph=createGraph(cost);
        return KruskalMST(graph);
    }
    public class Graph{
        HashMap<Integer,Node> nodes;
        HashSet<Edge> edges;
        public Graph(){
            nodes=new HashMap<>();
            edges=new HashSet<>();
        }
    }
    public class Node{
        int value;
        ArrayList<Node> nexts;
        ArrayList<Edge> edges;
        public Node(int value){
            this.value=value;
            nexts=new ArrayList<>();
            edges=new ArrayList<>();
        }
    }
    public class Edge{
        int weight;
        Node from;
        Node to;
        public Edge(int weight,Node from,Node to){
            this.weight=weight;
            this.to=to;
            this.from=from;
        }
    }
    public Graph createGraph(int[][] cost){
        Graph graph=new Graph();
        for(int i=0;i<cost.length;i++){
            int weight=cost[i][2];
            int from=cost[i][0];
            int to=cost[i][1];
            if(!graph.nodes.containsKey(from)){
                graph.nodes.put(from,new Node(from));
            }
            if(!graph.nodes.containsKey(to)){
                graph.nodes.put(to,new Node(to));
            }
            Node fromNode=graph.nodes.get(from);
            Node toNode=graph.nodes.get(to);
            Edge newEdge=new Edge(weight,fromNode,toNode);
            fromNode.nexts.add(toNode);
            fromNode.edges.add(newEdge);
            graph.edges.add(newEdge);
        }
        return graph;
    }
    public class UnionFind{
        HashMap<Node,Node> fatherMap;
        HashMap<Node,Integer> rankMap;
        public UnionFind(){
            fatherMap=new HashMap<Node,Node>();
            rankMap=new HashMap<Node,Integer>();
        }
        public Node findFather(Node n){
            Node father=fatherMap.get(n);
            if(father!=n){
                father=findFather(father);
            }
            fatherMap.put(n,father);
            return father;
        }
        public void makeSets(Collection<Node> nodes){
            fatherMap.clear();
            rankMap.clear();
            for(Node node:nodes){
                fatherMap.put(node,node);
                rankMap.put(node,1);
            }
        }
        public boolean isSameSet(Node a,Node b){
            return findFather(a)==findFather(b);
        }
        public void union(Node a,Node b){
            if(a==null||b==null){
                return;
            }
            Node aFather=findFather(a);
            Node bFather=findFather(b);
            if(aFather!=bFather){
                int aFrank=rankMap.get(aFather);
                int bFrank=rankMap.get(bFather);
                if(aFrank<=bFrank){
                    fatherMap.put(aFather,bFather);
                    rankMap.put(bFather,aFrank+bFrank);
                }
                else{
                    fatherMap.put(bFather,aFather);
                    rankMap.put(aFather,aFrank+bFrank);
                }
            }
        }
    }
    public class EdgeComparator implements Comparator<Edge>{
            public int compare(Edge o1,Edge o2){
                return o1.weight-o2.weight;
            }
        }
    public int KruskalMST(Graph graph){
        int sum=0;
        UnionFind unionFind=new UnionFind();
        unionFind.makeSets(graph.nodes.values());
        PriorityQueue<Edge> priorityQueue=new PriorityQueue<>(new EdgeComparator());
        for(Edge edge:graph.edges){
            priorityQueue.add(edge);
        }
        while(!priorityQueue.isEmpty()){
            Edge edge=priorityQueue.poll();
            if(!unionFind.isSameSet(edge.from,edge.to)){
                sum+=edge.weight;
                unionFind.union(edge.from,edge.to);
            }
        }
        return sum;
    }
}

prim算法却没过了,难受
编辑于 2021-04-24 16:12:19 回复(1)