python最小生成树算法_最小生成树算法

[音乐] [音乐] [音乐] [音乐] 各位大家好,本次介绍最小生成树相关知识点 一个小镇它一共有五座房子,有一天他们商量好 在房屋下面修建逃生通道使之互相相连 但是同时他们又希望开发代价比较小,在通过 和开发商协商之后,他们得到了如图所示的 这样一个开发代价信息图,那么请问最节省成本的开发方案是什么? 我们把这个问题形式化一下 首先需要引入两个定义,第一个是带权图 带权,我们称G是一个带权图,它包含三个信息 顶点信息、 边信息,以及一个新的权重信息 w是一个函数,它将对每一条边赋予一个权值 而最小生成树的问题是说,给了这样一个带权图之后 我们要找它所有生成树中代价最小的那棵树 有的时候我们对w(G)这样一个符号,我们使用w(G)这样一个符号来表示 G中所有边的权重之和 我们想强调一点就是,最小生成树不一定唯一 在图中的这样一个,注意我们这里用带圈的数字来表示顶点的名字,而把 权重直接标志在边上 我们说这个图它有两个不同的最小生成树,使用 ①②和②③边,或者是使用①③和②③边,它们的权重是一样,并且都是原图的 生成树。 我们 自然而然地会回忆Caley定理,Caley定理是说n个顶点能够形成的不同的树 一共有n的n-2次方种,当n=5的时候 n的n-2次方,表现为125种不同的生成树 在这个规模,计算机当然可以轻松地处理,我们找到原始问题中的 最小的这样开掘代价为红线所表示的这样一个开掘方案 但是如果这个城镇扩张了,它变成了十座房子,我们可以看到现在 根据Caley定理,就有10的8次方,有一亿种可能的开掘方案 那么当n=20的时候,我们的开掘方案的数量 猛增到了10的20次方,这就是一个天文数字,计算机无法处理 更不要说n继续增加的时候,但是我们想说 寻找最小生成树这个问题,其实有非常快速的算法 这也是我们本次课想要介绍的第一个算法,是Kruskal算法 Kruskal算法我们先通过一个实例来介绍它的运行,然后我们 会形式化地定义什么是Kruskal算法,并最后给出Kruskal算法正确性的证明 在给出的这张图中,一共有七个顶点 我们也在相应的边上,标上了权重 Kruskal算法它说我先把所有边的权重,按照一个永远不会递减的这样一个 顺序排好,我接下来的工作呢就是要把 这些边加回到这些顶点集合中,使得 右边的这些点,生成原来图的一棵树,一个生成树 首先我们使用的是原始图中权重最小的边 是1这个边,我们把它加入目标的生成树 我们接下来寻找在没有考虑过的边中 权重最小的是2,我们把2加入 目标生成树,下一个没有考虑并且最小的边的权重是3,我们把3加入 但是这个时候问题出现了,我们说新加入的这条边和原来的边形成了一个环,这个时候 我们把3这条边去掉,不使用。 我们继续寻找下一条没有使用过,并且权重最小的那个边 这里是4,我们把4加回来。 注意在这里的时候,原始的七个 独立的点,现在变成了四个连通分支 加了4之后,我们继续寻找下一个权重最小并且没使用过的是5,5加进来 我们接着,5之后是6,6又形成了环,我们不使用 我们使用7,7不形成环 再使用10,10又出现环,同样地,不再使用 我们继续寻找,一直找到了14这条边 注意到这个时候右边的图已经形成了原始的 图的一个生成树,我们说这个时候我们的算法终止 并且我们声称我们所找到的这棵树就是一棵最小生成树 好了,我们这里介绍一下Kruskal算法的形式化描述 Kruskal算法的输入是一个连通的带权图,而输出是这个连通带权图的最小生成树 我们这个算法怎么做呢?它第一步是先把E中的边 按照从小到大的顺序排好,或者叫做一个不递减的序这样一个排好 第二步我们初始化两个集合,一个是T集合,一个是E'集合,T集合其实是目标的 生成树集合,而E'是还未使用过的边集 我们说如果当T的规模小于n-1的时候,我们就继续在还没有考虑过的边集中 寻找权重最小的那个边,我们把这个边加入到T中 我们考察这样的一个图是否会含有环 如果没有环,那么我们说这个边的加入是 合适的,或者换句话说我们就在T中加入这条边,并且 将这条边从E'中移出 如果有环,那么我们就,T就不更新 这条边不能使用。 这个时候我们同样地需要更新E' 使得未使用过的边的集合减少1 所以大家可以看到,无论有环或者没环,E'的大小严格地减少 所以这个算法一定会终止,当这个算法终止的时候 我们输出最后的那个T,就是我们所要寻找的最小生成树 当然我们给出了算法,我们总是要说明它的正确性 这个正确性其实包含两个方面,第一个要说明这个算法它确实 确实会在n-1的时候 这个规模的时候停止,这个是比较容易说明的,因为大家可以看到 一开始我们的集合是所有的 n个顶点,而我们每一步,算法的每一步所做的事情 其实是每一次减少一个连通分支,那么 我们知道原始又是一个连通图,那么经过n-1步之后,它必然会 终止。 刚才我们提了 Kruskal算法一定会在T的规模正好等于n-1的时候停下来 接下来我们想要说,它生成的不仅是一棵树,并且是一棵最优的树 那么怎么样说明它是最优的树呢?那我们只需要说明Kruskal算法输出的这棵树 它的权重比任何一棵原图G的 生成树的权重都要小,那么就说明 我们这里用T来表示Kruskal算法的输出,并且 任取一棵生成树叫T0* 既然T和T0*可能不一样,那么我们用e来表示 依照Kruskal算法的顺序被加入T 中的第一条不在,不出现在Ti*中的边 注意,我这里用的是i,是为了后面好说明,一开始i就是0 e的两个边,顶点我们称为u和v 现在我们做了一件什么事?我们把e这个边加回到T0*中 但是又由于我们已经知道T0*是原始图的一棵生成树 所以我们知道u和v之间本来已经有一个边了 u和v之间已经有一条路径 那么又由于树是最大的 无环图,那么e的加入必定导致图中出现一个环 这里我们想说,从u到v的这个路径上,必然 用到了一条边,这条边呢它不出现在T中 这个很容易说明,因为如果u到v的所有 边都已经出现在T中的话,那么T本身就是含环,这也与我们的算法相违背 并且我们要说,这个e'它不仅仅是只出现在T0*中,并且它的权重 至少和e一样大,为什么?因为如果它的权重严格地小于e,那么 根据Kruskal算法的 设计,我们总是先使用权重比较小的边,那么e'之前就应该被用到 这个时候我们更新T0*,我们把它得到 Ti*,Ti*是从T0*中删掉e'这个边 而把e这个边加入,大家可以看到 因为我们加入了一条权重不会高于原来权重被删 掉的边的权重的边,所以新得到的这棵树它的权重不会比原来树的权重要 高。 这里我们得到一个最重要的观察。 我们说 T 和 T1* 相比,它 与 T0* 之间的差距又减少了 1 或者换一句话说 e 这条边共享于 T1* 和 T 之间 而且与此同时我们刚才这个 论证过程可以,可以一直往返 我们的每次得到新的这样一个,带 * 的这样一棵树,它的权重总不会比原来树的权重要 大,或者换句话说它总是小于等于原来树的那个权重,最后我们这个 T 最终会到达一棵树,它和这棵树之间是 完全是一样的,并且这棵树的权重一定是小于等于原始的 T0* 换句话说我们就证明了 T 这棵树的权重一定是小于等于任何一棵 原始图中生成树的权重,也就是说 T 是最优生成树 好了,我们回顾一下 Kruskal 算法。 Kruskal 算法的 核心是对边进行一个权重的排序。 然后我们每次都试图去使用那个 没使用过的权重最小的边。 那么我们想要介绍第二种算法叫 Prim 算法, Prim 算法 非常不一样,它是从点出发来考虑问题。 还是同样一个例子 但我们现在把点分成两个集合,第一个集合叫 X 集合,第二个集合叫做 Y 集合 开始的时候 X 集合只含有一个点,随便一个点,我们这里取 1,用黄色表示 而 Y 集合中的点就是除了 1 之外的顶点 这个时候我们要标记所有跨越在 X 集和 Y 集之间的边。 大家可以看到我用虚线表示了 跨越了 X 和 Y 之间的那个可能的边,它包含了 R 这条边 以及权重为 1 的这条边。 Prim 算法说我这个时候我取权重最小的那个边 我这里取 1 ,并且把这个边的另外一个顶点 2 也加入 X 集合 虚线中间所勾画出这个所包括就是 X 集合,余下的都是 Y 集 那么我们同样看 X 和 Y 这两个集合之间,跨这两个集合的边,权重最小的是哪一个呢? 在这里是 2,我们就进一步扩展 X,把 4 放进去 现在 X 包含了 3 个点,类似的 我们找到跨两个集合的边中,权值最小的是 5 把 7 放进去,继续寻找,找到最小的是 4 把 4 放进去,进一步扩大找到最小的 是 7,把 7 放进去。 我们到最后一步,我们为了把 5 放进去,我们观察 X 和 Y 跨越 X 和 Y 的那个边,权重最小是 14 这就是我们最后 Prim 算法的输出 可以看到它和 Kruskal 算法输出了同样一棵最小生成树 Prim 算法的形式化描述是这样,同样它的输入是一棵带权图 而它的输出是最小生成树。 算法描述式我们维持 三个集合。 第一个是 T 集合,是目标生成树集合。 第二个是 X 集合 初始化包含任何一个点。 我们一开始,所以不是一般性,我们假设它就 包含了标号为 1 的这个点。 Y 是说除了 X 中间 的点,我们把它都记为 Y 中的点 当 Y 还不为空的时候,那么我们怎么样做呢?我们去找 X 和 Y 这两个集合,跨越这两个集合的边中权值最小的边 我们把这个边记为 e,它的两个顶点分别记为 x 和 y。 我们假设 x 已经在 X 中,y 已经在 Y 中。 那么我们可以更新 X 是怎么样更新?我们将把这个 Y 中的这个点 y 放到 X 中,而与此同时 Y 中的点会减少一个 而 T 的更新是怎样的呢?我们就会把这个 所找到的这个跨越两个集合全是最小的边放到 T 中来 最后我们会输出 T,注意输出 T 的时候,就是说 Y 是空集,Y 是空集,所有的点都被使用到 可以看到 Prim 算法和 Kruskal 算法的思想还是非常不一样。 这里的重点的考察对象 是点。 我们想说 Prim 算法的正确性。 首先它显然会输出一个连通图,因为每一步 我们都是把一个点加入到 X 中,而 X 中本身可以 归纳的这么一个,一定是一个连通图 同时又由于我们每次所用的边都是跨了 X 和 Y 两个集合,所以我们一定不会引入环 好了,我们刚才分析了 Prim 算法,我们说算法每一步 都是在不断地生长一棵树,所以它的输出一定是一棵树 那么为了证明正确性呢,我们只需要证明它确实是权重 最小的这样一棵树。 类似于 Kruskal 算法的证明,我们假设 Prim 算法的输出 是 T,我们假设任何任取一棵原图集的生成树,我们用 T0* 来表示 我们假设如果 T 和 T0* 不一样 那么我们用 e 来表示,依照算法被加入 T 的第一条 不在 T0* 中的边,这里我仍然是用 Ti*,i 后来会被 不断地更新。 这个 e 的两个端点分别是 u 和 v 我们现在同样地把 e 这个边加入 T0* 中。 我们说因为原始的 T0* 是一棵树,所以说把 e 加入之后,它必然产生一个环 但是我们考虑在加入 e 之前 的 X 集合,X 集合因为它所对应的、 它所对应的 这样一个连通分支,它跟 Y 之间到底是什么关系呢? 因为我们刚刚提到了,加入 e 之后会有一个环,所以我们知道在 X 和 Y 之间 对 T0* 而言必然存在另外一条边是 e' 那么我们说,我们在 运行 Prim 算法的时候选择的加入 e,而没有选择 e' 的可能性只有一个,那么就是 e' 的权重 大于等于 e 这个边的权重 同样,类似 Kruskal 的算法的证明,我们这个时候更新 T0* 为 T1* T1* 说我们把 T0* 中原始的边 e' 删掉。 我们删掉一个权重 偏大一些的边,偏大或者相等的边,而加入一条新的边叫做 e 那么显然 T1* 的权重 不会高于 T0* 的权重,并且 T1* 一定也是一棵树。 因为我们 是从一个环中删掉了一个边,并且它仍然是 保持了原图的连通性。 这里我们同样 从 T0* 到 T1* 我们实现了一件什么事?从 T0* 到 T1* 它们跟 T 的相似度增加了 1,但是 T1* 的权重小于等于原始 T0* 的权重 我们可以继续这样的一个论证过程。 从 T0* 得到 T1* 得到 T2* 等等,直到 T 和某一个 T 阶星它们完全相等 但是又由于我们保持了权重这样一个不会增加的这样一个关系,所以我们得到 最后的 WT 阶星,它的权重一定是小于等于原始的 WT0* 换句话说,T 的权重一定是小于等于任何一棵原图的 生成树,也就证明了我们根据 Prim 算法 所得到的这个树 T 确实是一棵最优的生成树 这里我们就介绍好了有关最小生成树的定义,以及它的两个基本算法 谢谢大家!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值