字节面试官让我讲讲最小生成树,我忍不住笑了

简介: 字节面试官让我讲讲最小生成树,我忍不住笑了

一、引言

鸽了一周的我又回来了…

小黄去过生日了~

带你们看下生日蛋糕


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

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

如同下图所示:

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

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

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

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

当然,有人可能有疑惑,为什么不一次性把 Prim 算法也讲了,结尾我会告诉答案的

二、Kruskal 算法是什么

克鲁斯卡尔Kruskal)算法是求连通网的最小生成树的一种方法。与普里姆Prim)算法不同,它的时间复杂度为 O(eloge)(e为网中的边数),所以,适合于求边稀疏的最小生成树 。

因为我们的 Kruskal 算法是以边为单位,所以求一些边稀疏的最小生成树,时间复杂度比较小

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

三、Kruskal 算法本质

对于 Kruskal 算法来说,整体使用了 贪心 + 并查集 的思路

有不熟悉并查集的童鞋可以看一下这篇:三分钟带你学会并查集【含状态压缩】

简单来说,我们需要将所有的边放入一个堆中,按照边的大小进行排序,如下所示:1、2、3、6、7、10、12

  • 我们把第一个边 1 取出,将 C小区D小区 合并,目前集合:{C、D}

  • 我们把第二个边 2 取出,将 A小区E小区 合并,目前集合:{C、D}{A、E}

  • 我们把第三个边 3 取出,将 A小区B小区 合并,目前集合:{C、D}{A、B、E}

  • 将第四个边 6 取出,将 A小区D小区 合并,目前集合:{A、B、E、C、D}

  • 将第五个边 7 取出,将 B小区E小区 合并,由于 {A、B、E、C、D} 在一个集合,不进行合并,跳过该边
  • 将第六个边 10 取出,将 B小区C小区 合并,由于 {A、B、E、C、D} 在一个集合,不进行合并,跳过该边
  • 依次类推…

最终我们会得到一个路径,这也就是我们的最小生成树

由图得知,我们最小的资金需要:12

四、Kruskal 算法实现

对于 Kruskal 算法,我们需要实现两部分

  • 并查集
  • 贪心

1、并查集

这里简单的放下并查集的两个关键步骤,其余源码可以关注 爱敲代码的小黄 公众号,回复:算法源码 即可获得算法源码

合并

// 合并
    public void union(Node node1, Node node2) {
        // 找到两个节点的父节点
        Node node1Parent = getParentNode(node1);
        Node node2Parent = getParentNode(node2);
        // 看看是不是一个父亲
        if (node1Parent != node2Parent) {
            // node1、node2父亲的节点数量
            int size1 = size.get(node1Parent);
            int size2 = size.get(node2Parent);
            // 谁的节点多,少的就挂在多的下面,进行合并
            if (size1 >= size2) {
                parent.put(node1Parent, node2Parent);
                size.put(node1Parent, size1 + size2);
                size.remove(node2Parent);
            } else {
                parent.put(node2Parent, node1Parent);
                size.put(node2Parent, size1 + size2);
                size.remove(node2Parent);
            }
        }
    }

查询

public boolean isSame(Node node1, Node node2) {
        return getParentNode(node1) == getParentNode(node2);
    }
    public Node getParentNode(Node node) {
        // 为了路径压缩
        Stack<Node> stack = new Stack<>();
        while (parent.get(node) != node) {
            stack.add(node);
            node = parent.get(node);
        }
        while (!stack.isEmpty()) {
            parent.put(stack.pop(), node);
        }
        return node;
    }

2、Kruskal 算法

并查集的初始化

// 赋予初始值
    public void makeSets(Collection<Node> list) {
        for (Node node : list) {
          // 初始时,每个节点的父节点均是自己,集合的数量为1
            parent.put(node, node);
            size.put(node, 1);
        }
    }

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

public static class EdgeComparator implements Comparator<Edge> {
        @Override
        public int compare(Edge o1, Edge o2) {
            return o1.weight - o2.weight;
        }
    }

Kruskal 算法

public static Set<Edge> kruskalMST(Graph graph) {
        Union union = new Union();
        // 初始化并查集
        union.makeSets(graph.nodes.values());
    // 建堆,按照边的权重进行排序
        PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
        // 放入边
        for (Edge edge : graph.edges) {
            priorityQueue.add(edge);
        }
        Set<Edge> edges = new HashSet<>();
        // 从最小的开始
        while (!priorityQueue.isEmpty()) {
            Edge edge = priorityQueue.poll();
            // 看一下是否是一个集合的
            if (!union.isSame(edge.from, edge.to)) {
              // 可以选取这条边,合并这两个点
                edges.add(edge);
                union.union(edge.from, edge.to);
            }
        }
        return edges;
    }

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

五、总结

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

同时,对于 Kruskal 的代码也需要多写几遍,博主写这篇博客的时候,又忘记了怎么写(逃

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

回答一下一开始的问题:有人可能有疑惑,为什么不一次性把 Prim 算法也讲了

答:下期讲 Prim,可以水一期(逃

本期的内容就到这里,下期会讲述最小生成树 Prim 算法


相关文章
|
5月前
|
前端开发 JavaScript 安全
【前端面试字节ts的手写题】建议收藏!!!
【前端面试字节ts的手写题】建议收藏!!!
53 0
|
1月前
|
存储 安全 Java
面试题:用过ThreadLocal吗?ThreadLocal是在哪个包下的?看过ThreadLocal源码吗?讲一下ThreadLocal的get和put是怎么实现的?
字节面试题:用过ThreadLocal吗?ThreadLocal是在哪个包下的?看过ThreadLocal源码吗?讲一下ThreadLocal的get和put是怎么实现的?
36 0
|
5月前
|
算法 Java 程序员
火爆Boss直聘的2023最牛字节Java面试手册!助你狂拿千份offer!
当下程序员现状 根据一些调查报告,可以了解到当下程序员的现状。 首先,从年龄分布来看,年轻的程序员占据了主导地位。 30岁以下的开发者占比最高,为81%,而40岁以上的开发者仅占3%。 这意味着,程序员这个行业在一定程度上是年轻化的,同时也面临着一些中年转行或者技术更新换代的问题。 在性别方面,男性程序员的比例在90%以上,女性程序员的比例较低。 这可能和传统观念中将程序员视为男性职业有关。然而,随着技术的普及和女性对计算机科学的兴趣逐渐提高,女性程序员的比例也在逐渐增加。 从职业发展来看,程序员的职业发展相对较慢。 虽然程序员的薪资普遍较高,但是工作压力也很大,需要不断学习和更
93 0
|
8月前
|
存储 消息中间件 算法
字节面试的这道Java面试题各位能答上来吗:谈谈你对时间轮的理解?
一位工作了 7 年的程序员,去字节面试,被问到时间轮的问题。他说这个问题超出了他的知识面,自己也在网上也找了一些文章学习,但还是理解得不是很深刻。他希望让我出一期关于时间轮的面试题解析。今天,就给这位粉丝安排。
68 1
|
9月前
|
NoSQL Java 数据库连接
10 万字节Spring Boot +redis详细面试笔记(带完整目录)免费分享
2023年一个不平淡的一年,金九银十也快要开始了,各路码友们都开始磨拳擦脚,背面试题、知识点。小编最近得一良友赠送了一份关于SpringBoot,JVM篇,多线程&并发,Spring,MyBatis等
143 0
|
5月前
|
Java 关系型数据库 应用服务中间件
阿里最新春招面经,腾讯/美团/字节1万道Java中高级面试题
又是一年过去了,职场的积雪还没有消融,又迎来了一次大考。疫情还没完全过去,大家强打起精神,相互问好致意,眼角却满是疲惫...
|
5月前
|
算法 Java
记十次面试字节/美团失败总结的《520道LeetCode题Java版答案》
去字节、美团、BAT等大厂面试,刷LeetCode上的数据结构+算法题是必修课。许多读者说,刷题的时候经常会遇到困难,想要找一本答案题解做参考。
|
5月前
|
算法 程序员
凭借左程云(左神)的这份 “程序员代码面试指南”我入职了字节
左程云,本科就读于华中科技大学、硕士毕业于在芝加哥大学。先后在IBM、百度、GrowingIO和亚马逊工作,是一个刷题7年的算法爱好者,也是马士兵教育的算法授课老师。2014年起专职做程序员算法和数据结构培训,代码面试培训,刷题交流等相关工作。
|
5月前
|
存储 算法 NoSQL
“三顾字节”,九次面试,只要算法搞得好,大厂offer跑不了
4.29 字节春招截止倒数第二天,杭州Java商业变现部门暑假实习,隔天挂,春招结束(人生的第一份简历,嗯就开始即结束
|
5月前
|
NoSQL 算法 Java
985硕,秋招面试30家企业,怒斩阿里、字节、美团offer
6.1号开始投简历,7.6号开始第一场面试,9.30号收到最后一家意向书,我的秋招结束了! 找工作期间薅了网上不少大佬的羊毛,特别感谢期间给予帮助的各位前辈们。在此记录下秋招的全过程,也算是对帮助我的大佬们的回馈,十一假期期间码字,面试问题都排在后面(先看看我是如何一点点薅羊毛的),看得出我对帮助过我的大佬们的重视!(舔就完了,滋滋)