[亚马逊OA2] 最小生成树_MST

首先要感谢
白马书院的姑娘
提供的解题思路和面经。


MST:Minimum Spanning Tree

我都知道你们这些人都不愿意查资料,所以就先讲讲什么是MST。

这还是图论里的概念。

先来解释一下什么是Spanning Tree:

无向图中,如果存在一颗树,第一,它是这无向图的子图;第二,能够连接图中的所有节点。那么这棵树就称之为Spanning Tree。

Minimum Spanning Tree:

也就是在一个无向有权图(edge-weighted undirected graph)中,存在的一棵spanning tree,不成环的情况下连接所有的节点,并且具有最小权重总和,这颗树,就是MST。(节点数 = N,边数 = N - 1)

那么万恶的亚马逊会绕着弯给你出这个题。

会说,现在过节了,某一个地区有好多个城市(节点),这些城市都有电线互相连接,组成了一个电网(图)。每一条电线都要花钱,而且每一条花的钱都不一样(权重)。地方供电局想花费最小的给所有城市供电,请你给出一个解决方案。

抽象出来就是在无向权重图中找MST的题。我的解法也是根据Kruskal's Algorithm来的(维基百科里面解释得挺好的,有兴趣的同学可以去看看)。
算法里面涉及到Union Find,所以比较繁杂。大家做好准备一起来。


算法的基本思想

由于前一篇Order Dependency有同学说看不懂,我就尽量写得更好一点
先上图:

1.png

这里的MST:

AB,BC,CD三条边组成的spanning tree

再上例子图:

2.png
算法开始了:

首先把所有的边都列出来,根据权重排个序(来来来,写个快排)
大概会成这样:

  AD 1
  BC 1
  DC 1
  EF 2
  AB 3
  BD 3
  CF 4
  CE 5

同时,所有的节点(vertices)都各自成为独立的disjoint set。大概会成这样:

3.png

算法的基本规则就是:
在排好序的边里,从上往下进行遍历

  1. 当两个节点不在同一个set中的话,那么把它们分别所在的set合为一个set,并把这条边加入到最终结果中。
  2. 当两个节点在同一个set中的话,就不用合并,也不用加到最终结果中去。
那就来走一遍例子呗
  1. 边AD,A和D两个节点都不在同一个set中,那么就把它们合在一起,用set A来代表它们合并后的集合,并且把边AD加入结果里面。大概会成这样:
    4.png
  2. 边BC,B和C也不在同一个set里面,合起来,用set B表示,也加入结果中。大概会成这样:
    5.png
  3. 边DC,这两也不在同一个集合里,合并,用set A来表示。同时也把边DC放入结果中。大概会成这样:
    6.png
  4. 边EF,同理,合并,放入结果,用set E表示。大概也会成这样:
    7.png
  5. 边AB,这两个节点已经在同一个集合里了,忽略。
  6. 边BD,忽略。
  7. 边CF,两个节点不在同一个集合里,合并它们所在的集合,并且把边CF放入结果中,合并后的集合用set A来表示。大概是这样:
    8.png
  1. 这估计没人看了,略。
到此,算法就结束了,就找到了这张图的MST了:

AD, BC, DC, EF, CF
最后的权重和为9

说了这么多,上代码:
public class MST {
    //给定的类
    public static class Connection {
        String city1;
        String city2;
        int cost;
        public Connection(String a, String b, int c) {
            city1 = a;
            city2 = b;
            cost = c;
        }
    }

    /**
     * 为了避免使用全局变量,声明一个UnionFind类
     */
    public static class UnionFind {
        Map<String, Integer> map; //map中装的是城市->城市所在的集合代号
        int setNum; //城市集合代号
        public UnionFind() {
            map = new HashMap<>();
            setNum = 0;
        }

        /**
         * 对每一个connection做union操作
         * 如果没有union操作,返回FALSE,如果有union操作,返回TRUE
         * 这里跟算法描述中不太一样:这里合并过的城市才会被分配集合代号
         */
        public boolean union(Connection conn) {
            // 两个城市都没有城市代号(都存在于单独的集合,都没有被合并过)
            if (!map.containsKey(conn.city1) && !map.containsKey(conn.city2)) {
                map.put(conn.city1, setNum);
                map.put(conn.city2, setNum);
                setNum++;
                return true;
            }
            // 有一个城市有代号(说明其中有一个是之前合并过的)
            if (map.containsKey(conn.city1) && !map.containsKey(conn.city2)) {
                map.put(conn.city2, map.get(conn.city1));
                return true;
            }
            if (!map.containsKey(conn.city1) && map.containsKey(conn.city2)) {
                map.put(conn.city1, map.get(conn.city2));
                return true;
            }
            // 两个都有代号(那么合并它们分别所在的集合中的所有城市)
            if (map.containsKey(conn.city1) && map.containsKey(conn.city2)) {
                int num1 = map.get(conn.city1);
                int num2 = map.get(conn.city2);
                if (num1 == num2) { //避免出现环
                    return false;
                }
                for (String city : map.keySet()) { //把city1在集合中的所有城市代号改为city2的代号
                    if (map.get(city) == num1) {
                        map.put(city, num2);
                    }
                }
                return true;
            }
            return false;
        }
    }

    /**
     * Time: O(ElogE + E) 后面的 "+E"是在union函数中,当两个城市都有代号的时候
     * Space: O(E)
     */
    public static List<Connection> findMinimum(List<Connection> list) {
        List<Connection> result = new ArrayList<>(); //最终结果,输出必须排序
        if (list == null || list.size() == 0) {
            return result;
        }
        UnionFind uf = new UnionFind();

        // 首先把边以权重来排序
        Collections.sort(list, new Comparator<Connection>() {
            @Override
            public int compare(Connection conn1, Connection conn2) {
                return conn1.cost - conn2.cost;
            }
        });

        // 遍历每一条边,进行处理
        for (Connection conn : list) {
            if (uf.union(conn)) {
                result.add(conn);
            }
        }

        // 最后把结果再排序一次
        Collections.sort(result, new Comparator<Connection>() {
            @Override
            public int compare(Connection conn1, Connection conn2) {
                if (conn1.city1.equals(conn2.city1)) {
                    return conn1.city2.compareTo(conn2.city2);
                }
                return conn1.city1.compareTo(conn2.city1);
            }
        });
        return result;
    }

    public static void main(String[] args) {
        Connection c1 = new Connection("A", "D", 1);
        Connection c2 = new Connection("A", "B", 3);
        Connection c3 = new Connection("D", "B", 3);
        Connection c4 = new Connection("B", "C", 1);
        Connection c5 = new Connection("C", "D", 1);
        Connection c6 = new Connection("E", "D", 6);
        Connection c7 = new Connection("C", "E", 5);
        Connection c8 = new Connection("C", "F", 4);
        Connection c9 = new Connection("E", "F", 2);
        List<Connection> list = new ArrayList<>(Arrays.asList(c1, c2, c3, c4, c5, c6, c7, c8, c9));
        List<Connection> result = findMinimum(list);
        for (Connection conn : result) {
            System.out.println(conn.city1 + "-" + conn.cost + "-" + conn.city2);
        }
    }
}

时间复杂度:O(ElogE + E)
空间复杂度:O(E)

这道题没有用全局变量,但是在亚麻的测试中,有两个test case没有通过,要是哪位大神看出错在哪里,请用最蔑视的语气告诉我哪里错了,谢谢了~

看到这儿了都不点个赞,是不是有点说不过去了?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容