一文带你学会最小生成树,不要等到面试再临时抱佛脚了

前言

你好,我是小黄,一名独角兽企业的Java开发工程师。 感谢茫茫人海中我们能够相遇, 俗话说:当你的才华和能力,不足以支撑你的梦想的时候,请静下心来学习, 希望优秀的你可以和我一起学习,一起努力,实现属于自己的梦想。

一、引言

大家有没有在生活中遇到这种事情

你们县城需要在几个小区之间进行修路,由于政府资金紧张,不可能所有的小区之间都进行修路,而是利用最少的资金修一条可以连接所有小区的路

如同下图所示:

当然,上述只是一个抽象化的例子,而我们实际生活中,每个小区间的距离也是不一样的,我们怎么使用最小的资金去连接所有的小区呢?

这就牵扯到我们今天的老大哥们:Kruskal 算法和 Prim 算法

这两种算法分别从边和点产生最小生成树,保证了资金的最小性

本篇文章,我们一起走近 Prim 算法,探究一下该算法是怎么通过点来确定最小生成树的

PS:Kruskal 算法链接:最小生成树——Kruskal 算法

二、Prim 算法是什么

普里姆算法(Prim算法),图论中的一种算法,通过 <stron> 的行为,可在加权连通图里搜索最小生成树。</stron>

该算法于1930年由捷克数学家 沃伊捷赫·亚尔尼克 发现;并在1957年由美国计算机科学家 罗伯特·普里姆 独立发现

我们以下面的小区为例,通过 Prim 算***给我们一条连接所有小区的最短路径

三、Prim 算法本质

对于 Prim 算法来说,整体使用了 贪心 的思想

简单的来说,我们需要从上述图中随机挑选一个点,找到最小的边,然后释放其连接的点,再去找到最小的边,周而复始......

PS:每个边只入队一次

  • 我们随机选中 A小区 这个节点,将其所有的边 2、3、6 释放出来,我们的边集合为 2、3、6,找到最小的边 2 用掉,释放 E小区

  • 我们选中 E小区 这个节点,将其所有的边 7、12 释放出来,我们的边集合为 3、6、7、12,找到最小的边 3 用掉,释放 B小区

  • 我们选中 B小区 这个节点,将其所有的边 10 释放出来,我们的边集合为 6、7、10、12,找到最小的边 6 用掉,释放 D小区

  • 我们选中 D小区 这个节点,将其所有的边 1 释放出来,我们的边集合为 1、7、10、12,找到最小的边 1 用掉,释放 C小区

这样,我们所有的点已经找出来了

可以看到我们的最小生成树为:12

我们对比一下上期讲到的 Kruskal 算法:Kruskal 算法 

 我们可以看到,得到的路径都是一样的,证明 Kruskal 和 Prim 算法求出的最小生成树相同

四、Prim 算法实现

对于 Prim 算法,主要利用贪心的思想,由点去寻找边,解锁点的循环.....

4.1 比较器的实现(按照边的权重排序)

public static class MyEdgeComparator implements Comparator<Edge> {

        @Override
        public int compare(Edge o1, Edge o2) {
            return o1.weight - o2.weight;
        }
    }
复制代码

4.2 Prim 算法

	public Set<Edge> prim(Graph graph) {
        // 解锁的边
        PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new MyEdgeComparator());

        // 该点是否走过
        Set<Node> setNodeVis = new HashSet<>();


        // 挑选的边
        Set<Edge> setEdge = new HashSet<>();

        for (Node node : graph.nodes.values()) {
            // 该点没有被走过
            if (!setNodeVis.contains(node)) {
                // 将该点标记为已经被走过
                setNodeVis.add(node);
                // 加入该点解锁的边
                for (Edge edge : node.edges) {
                    priorityQueue.add(edge);
                }
				
				// 由边找点
                while (!priorityQueue.isEmpty()) {
                    Edge edge = priorityQueue.poll();
                    Node to = edge.to;
                    // 看当前点是否被访问过
                    if (!setNodeVis.contains(to)) {
                    	// 最终的结果
                        setEdge.add(edge);
                        // 没有访问的话,加入访问标记,并且将其所有的边给放进去
                        setNodeVis.add(to);
                        for (Edge edge1 : to.edges) {
                            priorityQueue.add(edge1);
                        }
                    }
                }
            }
            // 大家可以思考一下这里的 break 存在的意义是什么
            break;
        }
        return setEdge;
    }
复制代码

以上图的描述均使用图的形象化描述:图的形象化描述

五、总结

通过以上的描述,我们可以解决我们开头说的那个问题:你们县城需要在几个小区之间进行修路,由于政府资金紧张,不可能所有的小区之间都进行修路,而是利用最少的资金修一条可以连接所有小区的路

同时,对于 Prim 的代码也需要多写几遍,多想想 Kruskal 和 Prim 的区别

对源代码有兴趣的小伙伴,可以关注 爱敲代码的小黄 公众号,回复:算法源码 即可获得算法源码

回答一下 break 的问题:防止森林现象的存在

本期的内容就到这里,下期会讲述最短路径 Dijkstra

全部评论

相关推荐

家人们,我现在真的好纠结。我是26届的,目前还没有实习过。我现在的情况是,想参加秋招,但是感觉自己的简历特别空,没有实习经历会不会秋招直接凉凉啊?可我又听说现在很多公司对26届实习生也不太感冒,说什么不确定性大。而且我最近在准备考公,时间上也有点冲突。要是把时间花在实习上,备考时间就少了。但要是不实习,又怕以后就业有问题😫有没有懂行的友友帮我分析分析:26届现在不实习,秋招找工作真的会很难吗?考公和实习该怎么平衡啊?如果现在不实习,考完公再去找实习还来得及吗?真的太焦虑了,希望大家能给我点建议🙏
小破站_程序员YT:我可能和大家的观点不一样。人的精力是有限的,不能既要还要。你又想实习又想考公最后又要秋招上岸,我觉得哪有那么多的选择。你如果想考上岸,那就全力以赴。如果想秋招上岸,就继续投实习,投没了,就继续准备秋招,秋招不行继续春招。别到最后,考公没上岸,觉得是花了时间浪费在找实习上了, 秋招没上岸,觉得是浪费时间准备考公去了。我是认为很难说可以去平衡 不喜勿喷,可以叫我删除
点赞 评论 收藏
分享
迟缓的斜杠青年巴比Q了:简历被投过的公司卖出去了,我前两天遇到过更离谱的,打电话来问我有没有意向报班学Java学习,服了,还拿我学校一个学长在他们那报班学了之后干了华为OD当招牌
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务