知识大全 贪婪算法之——最小耗费生成树
Posted 知
篇首语:新的一天你要加油,不要辜负你的一生,让自己活得更有意义。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 贪婪算法之——最小耗费生成树相关的知识,希望对你有一定的参考价值。
在例 及 中已考察过这个问题 因为具有n 个顶点的无向网络G的每个生成树刚好具有n 条边 所以问题是用某种方法选择n 条边使它们形成G的最小生成树 至少可以采用三种不同的贪婪策略来选择这n 条边 这三种求解最小生成树的贪婪算法策略是 K r u s k a l算法 P r i m算法和S o l l i n算法
Kruskal算法
( ) 算法思想
K r u s k a l算法每次选择n 条边 所使用的贪婪准则是 从剩下的边中选择一条不会产生环路的具有最小耗费的边加入已选择的边的集合中 注意到所选取的边若产生环路则不可能形成一棵生成树 K r u s k a l算法分e 步 其中e 是网络中边的数目 按耗费递增的顺序来考虑这e 条边 每次考虑一条边 当考虑某条边时 若将其加入到已选边的集合中会出现环路 则将其抛弃 否则 将它选入
考察图 a 中的网络 初始时没有任何边被选择 图 b 显示了各节点的当前状态 边( )是最先选入的边 它被加入到欲构建的生成树中 得到图 c 下一步选择边( )并将其加入树中(如图 d所示) 然后考虑边( ) 将它加入树中并不会产生环路 于是便得到图 e 下一步考虑边( )并将其加入树中(如图 f所示) 在其余还未考虑的边中 ( )具有最小耗费 因此先考虑它 将它加入正在创建的树中会产生环路 所以将其丢弃 此后将边( )加入树中 得到的树如图 g 所示 下一步考虑边( ) 由于会产生环路 将其丢弃 最后考虑边( )并将其加入树中 产生了一棵生成树 其耗费为 图 给出了K r u s k a l算法的伪代码
/ /在一个具有n 个顶点的网络中找到一棵最小生成树
令T为所选边的集合 初始化T=
令E 为网络中边的集合
w h i l e(E≠ )&&(| T |≠n )
令(u v)为E中代价最小的边 E=E (u v) / /从E中删除边
i f( (u v)加入T中不会产生环路)将( u v)加入T
i f(| T | = =n ) T是最小耗费生成树
e l s e 网络不是互连的 不能找到生成树
图 Kruskao算法的伪代码
( ) 正确性证明
利用前述装载问题所用的转化技术可以证明图 的贪婪算法总能建立一棵最小耗费生成树 需要证明以下两点 ) 只要存在生成树 K r u s k a l算法总能产生一棵生成树; ) 产生的生成树具有最小耗费 令G为任意加权无向图(即G是一个无向网络) 从 节可知当且仅当一个无向图连通时它有生成树 而且在Kruskal 算法中被拒绝(丢弃)的边是那些会产生环路的边 删除连通图环路中的一条边所形成的图仍是连通图 因此如果G在开始时是连通的 则T与E中的边总能形成一个连通图 也就是若G开始时是连通的 算法不会终止于E= 和| T |< n
现在来证明所建立的生成树T具有最小耗费 由于G具有有限棵生成树 所以它至少具有一棵最小生成树 令U为这样的一棵最小生成树 T与U都刚好有n 条边 如果T=U 则T就具有最小耗费 那么不必再证明下去 因此假设T≠U 令k(k > ) 为在T中而不在U中的边的个数 当然k 也是在U中而不在T中的边的数目 通过把U变换为T来证明U与T具有相同的耗费 这种转化可在k 步内完成 每一步使在T而不在U中的边的数目刚好减 而且U的耗费不会因为转化而改变 经过k 步的转化得到的U将与原来的U具有相同的耗费 且转化后U中的边就是T中的边 由此可知 T具有最小耗费 每步转化包括从T中移一条边e 到U中 并从U中移出一条边f 边e 与f 的选取按如下方式进行
) 令e 是在T中而不在U中的具有最小耗费的边 由于k > 这条边肯定存在
) 当把e 加入U时 则会形成唯一的一条环路 令f 为这条环路上不在T中的任意一条边
由于T中不含环路 因此所形成的环路中至少有一条边不在T中
从e 与f 的选择方法中可以看出 V=U+ e f 是一棵生成树 且T中恰有k 条边不在V中出现 现在来证明V的耗费与U的相同 显然 V的耗费等于U的耗费加上边e 的耗费再减去边f 的耗费 若e 的耗费比f 的小 则生成树V的耗费比U的耗费小 这是不可能的 如果e 的耗费高于f 在K r u s k a l算法中f 会在e 之前被考虑 由于f 不在T中 Kruskal 算法在考虑f 能否加入T时已将f 丢弃 因此f 和T中耗费小于或等于f 的边共同形成环路 通过选择e 所有这些边均在U中 因此U肯定含有环路 但是实际上这不可能 因为U是一棵生成树 e 的代价高于f 的假设将会导致矛盾 剩下的唯一的可能是e 与f 具有相同的耗费 由此可知V与U的耗费相同
( ) 数据结构的选择及复杂性分析
为了按耗费非递减的顺序选择边 可以建立最小堆并根据需要从堆中一条一条地取出各边 当图中有e 条边时 需花(e) 的时间初始化堆及O ( l o ge) 的时间来选取每一条边 边的集合T与G中的顶点一起定义了一个由至多n 个连通子图构成的图 用顶点集合来描述每个子图 这些顶点集合没有公共顶点 为了确定边( u v)是否会产生环路 仅需检查u v 是否在同一个顶点集中(即处于同一子图) 如果是 则会形成一个环路;如果不是 则不会产生环路 因此对于顶点集使用两个F i n d操作就足够了 当一条边包含在T中时 个子图将被合并成一个子图 即对两个集合执行U n i o n操作 集合的F i n d和U n i o n操作可利用 节的树(以及加权规则和路径压缩)来高效地执行 F i n d操作的次数最多为 e Un i o n操作的次数最多为n (若网络是连通的 则刚好是n 次) 加上树的初始化时间 算法中这部分的复杂性只比O (n+e) 稍大一点
对集合T所执行的唯一操作是向T中添加一条新边 T可用数组t 来实现 添加操作在数组的一端进行 因为最多可在T中加入n 条边 因此对T的操作总时间为O (n)
总结上述各个部分的执行时间 可得图 算法的渐进复杂性为O (n+el o ge)
( ) 实现
利用上述数据结构 图 可用C + +代码来实现 首先定义E d g e N o d e类(见程序 ) 它是最小堆的元素及生成树数组t 的数据类型
程序 Kruskal算法所需要的数据类型
template
class EdgeNode
p u b l i c :
operator T() const return weight;
p r i v a t e :
T weight;//边的高度
int u v;//边的端点
;
为了更简单地使用 节的查找和合并策略 定义了U n i o n F i n d类 它的构造函数是程序 的初始化函数 U n i o n是程序 的加权合并函数 F i n d是程序 的路径压缩搜索函数
为了编写与网络描述无关的代码 还定义了一个新的类U N e t Wo r k 它包含了应用于无向网络的所有函数 这个类与U n d i r e c t e d类的差别在于U n d i r e c t e d类中的函数不要求加权边 而U N e t Wo r k要求边必须带有权值 U N e t Wo r k中的成员需要利用N e t w o r k类中定义的诸如B e g i n和N e x t Ve r t e x的遍历函数 不过 新的遍历函数不仅需要返回下一个邻接的顶点 而且要返回到达这个顶点的边的权值 这些遍历函数以及有向和无向加权网络的其他函数一起构成了W N e t w o r k类(见程序 )
程序 WNeork类
template
class WNeork : virtual public Neork
public :
virtual void First(int i int& j T& c)= ;
virtual void Next(int i int& j T& c)= ;
;
象B e g i n和N e x t Ve r t e x一样 可在A d j a c e n c y W D i g r a p h及L i n k e d W D i g r a p h类中加入函数F i r s t与N e x t 现在A d j a c e n c y W D i g r a p h及L i n k e d W D i g r a p h类都需要从W N e t Wo r k中派生而来 由于A d j a c e n c y W G r a p h类和L i n k e d W G r a p h类需要访问U N e t w o r k的成员 所以这两个类还必须从U N e t Wo r k中派生而来 U N e t Wo r k : : K r u s k a l的代码见程序 它要求将Edges() 定义为N e t Work 类的虚成员 并且把U N e t Wo r k定义为E d g e N o d e的友元) 如果没有生成树 函数返回f a l s e 否则返回t r u e 注意当返回true 时 在数组t 中返回最小耗费生成树
程序 Kr u s k a l算法的C + +代码
template
bool UNeork ::Kruskal(EdgeNode t[])
// 使用K r u s k a l算法寻找最小耗费生成树
// 如果不连通则返回false
// 如果连通 则在t [ : n ]中返回最小生成树
int n = Ve r t i c e s ( ) ;
int e = Edges();
/ /设置网络边的数组
InitializePos(); // 图遍历器
EdgeNode *E = new EdgeNode [e+ ];
int k = ; // E的游标
for (int i = ; i <= n; i++) // 使所有边附属于i
int j;
T c;
First(i j c);
while (j) // j 邻接自i
if (i < j) // 添加到达E的边
E[++k] weight = c;
E[k] u = i;
E[k] v = j;
Next(i j c);
// 把边放入最小堆
MinHeap > H( );
H Initialize(E e e);
UnionFind U(n); // 合并/搜索结构
相关参考
普里姆(Prim)算法 ()算法思想 T=(UTE)是存放MST的集合 ①T的初值是(r¢) 即最小生成树初始时只有一个红点r没有红边 ②T经过n次如下步骤操作最后得到一棵含n个顶点n条
克鲁斯卡尔(Kruskal)算法 ()算法思想 ①T的初始状态 只有n个顶点而无边的森林T=(V¢) ②按边长递增的顺序选择E中的n安全边(uv)并加入T生成MST 注意 安全边指两个
在这个问题中给出有向图G它的每条边都有一个非负的长度(耗费)a[i][j]路径的长度即为此路径所经过的边的长度之和对于给定的源顶点s需找出从它到图中其他任意顶点(称为目的)的最短路径图a给出了一个
最小生成树的概念和应用背景 最小生成树(MinimumSpanningTree)各边权的总和最小的生成树 MST性质 假设N=(VE)是一个连通网U是顶点集V的一个非空子集若(UV)是一条具有最
最小生成树 对于连通的带权图(连通网)G其生成树也是带权的生成树T各边的权值总和称为该树的权记作 > 这里: TE表示T的边集 w(uv)表示边(uv)的权 权最小的生成树称为G的最小
一个复杂的工程通常可以分解成一组小任务的集合完成这些小任务意味着整个工程的完成例如汽车装配工程可分解为以下任务将底盘放上装配线装轴将座位装在底盘上上漆装刹车装门等等任务之间具有先后关系例如在装轴之
在/背包问题中需对容量为c的背包进行装载从n个物品中选取装入背包的物品每件物品i的重量为wi价值为pi对于可行的背包装载背包中物品的总重量不能超过背包的容量最佳装载是指所装入的物品价值最高即n?i
树(自由树)无序树和有根树 自由树就是一个无回路的连通图(没有确定根)(在自由树中选定一顶点做根则成为一棵通常的树) 从根开始为每个顶点(在树中通常称作结点)的孩子规定从左到右的次序则它就成为
Prim(普里姆)算法适用于求______的网的最小生成树kruskal(克鲁斯卡尔)算法适用于求______的网的最小生成树【厦门大学一】 .克鲁斯卡尔算法的时间复杂度为______它对___
二分图是一个无向图它的n个顶点可二分为集合A和集合B且同一集合中的任意两个顶点在图中无边相连(即任何一条边都是一个顶点在集合A中另一个在集合B中)当且仅当B中的每个顶点至少与A中一个顶点相连时A的