Leetcode1584. 连接所有点的最小费用——稠密图的最小生成树

引入

最近开始学习Golang了,所以以后的Leetcode题解都用Golang来写。(ps:Golang没有基础的数据结构,如List、Queue、Stack、PriorityQueue等等,所以这个想法可以放弃掉了)
上周周赛有这么一道题1584. 连接所有点的最小费用,以前没有遇到过最小生成树的题,并且本题还是稠密图(即一个节点到各个节点都是联通的,反之如果联通的节点树木比较少,称为稀疏图),本题具有代表意义。

图的节点之间是相互联通的,联通的距离为曼哈顿距离,需要返回一个“连接所有点的最小总距离”,即构建一个最小生成树

构建方式一:Prim算法

  • 输入:一个加权连通图,其中节点集合为 V V V,边集合为 E E E
  • 初始化: V c u r r = { x } V_{curr} = \{x\} Vcurr={x},其中x为集合V中的任一节点(起始点), E c u r r = { } E_{curr} = \{\} Ecurr={},即初始化边为空;
  • 过程:
    1. 在边集合 E E E中选取距离最小的边 < x , y > <x,y> <x,y>,其中 x x x为集合 V c u r r V_{curr} Vcurr中的元素,而 y y y不在集合里;
    2. y y y加入到集合 V c u r r V_{curr} Vcurr中,将 < x , y > <x,y> <x,y>加入到集合 E c u r r E_{curr} Ecurr中;
    3. 重复步骤1,2直到 V c u r r = V V_{curr}=V Vcurr=V

很容易理解,不做赘述。

构建方式二:Kruskal算法

  • 输入:一个加权连通图 G G G,其中节点集合为 V V V,边集合为 E E E
  • 初始化:
    1. 新建一个图 G n e w G_{new} Gnew,该图具备与原图中相同的节点集合 V V V,但没有边
    2. 将原图 G G G的e个边按权值大小从小到大排序。
  • 过程:从权值最小的边开始遍历每条边,如果这条边上的两个节点 < x , y > <x,y> <x,y> G n e w G_{new} Gnew中不在一个联通分量中,那么将这条边添加到图 G n e w 中 G_{new}中 Gnew

过程里关键词其实是联通分量,怎么理解这个联通分量呢?可以简单的理解为集合: 在新的图 G n e w G_{new} Gnew上能够互通的两个节点,处于同一个联通分量上

参考:最小生成树算法

本题题解

在知晓这两个算法之后,如何解呢?

在稠密图的情况下,Prim算法会有更好的性能,因为用Kruskal要记录的边太多了,有 n ( n − 1 ) n(n-1) n(n1)个。

import java.util.HashSet;
import java.util.PriorityQueue;
import java.util.Set;

public class Solution {
    public int minCostConnectPoints(int[][] points) {
        int Len = points.length;
        int[][] distances = new int[Len][Len];

        //计算曼哈顿距离
        for (int i = 0; i < Len; i++) {
            int[] p1 = points[i];
            for (int j = 0; j < i; j++) {
                int[] p2 = points[j];
                distances[i][j] = Math.abs(p1[0] - p2[0]) + Math.abs(p1[1] - p2[1]);
                distances[j][i] = distances[i][j];
            }
        }

        //PRIM算法
        Set<Integer> set = new HashSet<>();//标记Gnew已经加入了哪些点
        PriorityQueue<Dis> priorityQueue = new PriorityQueue<>((o1, o2) -> o1.distance - o2.distance);

        //加入初始点
        set.add(0);
        for (int i = 1; i < Len; i++) {
            priorityQueue.add(new Dis().addDistance(distances[0][i]).addPointer(0, i));
        }

        int sum = 0;
        //Prim算法开始
        while (set.size() < Len) {
            //找出priorityQueue里面最小的
            Dis curr = priorityQueue.poll();
            if (set.contains(curr.pointers[0]) && set.contains(curr.pointers[1])) {
                continue;
            } else {
                sum += curr.distance;
                if (!set.contains(curr.pointers[0])) {
                    set.add(curr.pointers[0]);
                    for (int i = 0; i < Len; i++) {
                        if (i != curr.pointers[0]) {
                            priorityQueue.add(new Dis()
                                    .addDistance(distances[i][curr.pointers[0]])
                                    .addPointer(i, curr.pointers[0]));
                        }
                    }
                } else {
                    set.add(curr.pointers[1]);
                    for (int i = 0; i < Len; i++) {
                        if (i != curr.pointers[1]) {
                            priorityQueue.add(new Dis()
                                    .addDistance(distances[i][curr.pointers[1]])
                                    .addPointer(i, curr.pointers[1]));
                        }
                    }
                }
            }
        }

        return sum;
    }


    class Dis {
        public int distance;
        public int[] pointers = new int[2];

        Dis addDistance(int distance) {
            this.distance = distance;
            return this;
        }

        Dis addPointer(int a, int b) {
            pointers[0] = a;
            pointers[1] = b;
            return this;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值