算法: 最小生成树

简介

最小生成树 (Minimum Spanning Tree) 应该大家都不陌生,当然还有最大生成树啦,这篇文章就简单总结一下算法里的生成树。

什么是生成树

Spanning 有跨越的意思,生成树一般来说每个节点都能访问到别的节点,是一个连通树。所以,一般考虑无向图里去造生成树。生成树又分最小和最大两种,其中最小生成树应用比较多。总结一下生成树的定义:

  1. 首先它得是一个树的结构
  2. 所有的节点都能互相访问
  3. 要么最小要么最大(废话)

如下图所示,加粗的黑边就是最小生成树。

特性

生成树有两个看起来很废话的特性 Cycle Property 和 Cut Property,这里以最小生成树为主来说明。

Cycle Property

如果在一个环里,其中某条边 e 是这里面的权重最大的边,那么这条边一定不在最小生成树里。

举个例子,上图我们看到其中一个环 0 -> 1 -> 7 -> 0,我们发现边 1 -> 7 是这里面权重最大的,那么就一定不会在这张图的最小生成树里。

Cut Property

再来看看 Cut Property,如果你把图里的节点分成两堆,里面最权重最小的边是一定会在这张图的最小生成树里的。

如下图所示,边 f 是权重最小的连接两堆节点的边,这条边一定在最小生成树里。

上面两个特性是不是感觉很废话?但是这是后面造最小生成树的基础。当然最大生成树取反就好了。

算法

下面说说通常找最小生成树的算法,这些算法都能使用上面生成树的两个特性来证明其正确性。

Kruskal

  1. 首先获取所有的边,都这些边进行从小到大的排序
  2. 然后不断往图里添加边,并且避免造成环
  3. 直到所有点都能互相访问后停止算法

伪代码如下:

function kruskal(graph) {
    // 对图里的边进行排序
    const sortedEdges = graph.edges.sort()

    // 去掉图里的边
    graph.removeAllEdges()

    for (let i = 0; i < sortedEdges.length; i++) {
        graph.addEdge(sortedEdges[i])
        // 如果添加的边造成环,那么就移除
        if (graph.hasCycle()) {
            graph.removeEdges(sortedEdges[i])
        }
    }

    return graph
}

例子如下

这个算法的时间复杂度是 O(m\log(m))

Jarnik (Prim)

可能大家都知道 Prim,其实它还有一个名字是 Jarnik。算法步骤如下:

  1. 从一个节点(随便选一个)开始,去找这个节点相邻最短的边
  2. 将找到的边添加到这个节点上,这就形成一个组件了
  3. 再从这个组件开始去找相邻权重最小的边,再添加到这个组件上,不断重复直到所有节点都能被访问

伪代码如下:

function prim(graph) {
    const visited = []
    // 从一个节点开始
    const sourceComponent = graph.randomVertex()

    // 如果还有节点不能被访问就继续找
    while (visited.length < graph.vertices.length) {
        // 将相邻权重最小的边作为组件的一部分,且不构成环
        const smallestEdge = sourceComponent.findSmallestEdge()
        sourceComponent.addEdge(smallestEdge)

        visited.push(smallestEdge.toVertex)
    }
}

下面的例子就是使用 Prim 算法来找到最小生成树的。

这个算法的时间复杂度是 O(m\log(m))

Boruvka

Boruvka 算法又叫作 Sollin 算法,它其实是 Prim 算法的变种,Prim 是从一个节点开始找最小边,而 Boruvka 是所有节点一起找最小边。具体步骤如下:

  1. 首先也是像上面一样去掉全部的边,每个节点都是一个组件(森林)
  2. 对于每个组件(森林)都找其相邻权重最小的边,将这些边添加到组件上,不断重复直到所有的节点都能被访问
  3. 这里对添加的边要做一个是否构成环的判断

伪代码如下:

function boruvka(graph) {
    const visited = []
    const components = graph.vertices

    while (visited.length < graph.vertices.length) {
        for (let i = 0; i < components.length; i++) {
            // 找到相邻最小边
            const smallestEdge = components[i].findSmallestEdge()
            components[i].addEdge(smallestEdge)

            visited.push(smallestEdge.toVertex)

            // 合并某些组件
            combine(components)
        }
    }
}

下面例子使用 Boruvka 来找最小生成树。

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

推荐阅读更多精彩内容