贪婪算法2.docx

上传人:b****7 文档编号:9219584 上传时间:2023-02-03 格式:DOCX 页数:5 大小:19.23KB
下载 相关 举报
贪婪算法2.docx_第1页
第1页 / 共5页
贪婪算法2.docx_第2页
第2页 / 共5页
贪婪算法2.docx_第3页
第3页 / 共5页
贪婪算法2.docx_第4页
第4页 / 共5页
贪婪算法2.docx_第5页
第5页 / 共5页
亲,该文档总共5页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

贪婪算法2.docx

《贪婪算法2.docx》由会员分享,可在线阅读,更多相关《贪婪算法2.docx(5页珍藏版)》请在冰豆网上搜索。

贪婪算法2.docx

贪婪算法2

贪婪算法

(2)

最小耗费生成树在例1-2及1-3中已考察过这个问题。

因为具有n个顶点的无向网络G的每个生成树刚好具有n-1条边,所以问题是用某种方法选择n-1条边使它们形成G的最小生成树。

至少可以采用三种不同的贪婪策略来选择这n-1条边。

这三种求解最小生成树的贪婪算法策略是:

Kruskal算法,Prim算法和Sollin算法。

1.Kruskal算法

(1)算法思想Kruskal算法每次选择n-1条边,所使用的贪婪准则是:

从剩下的边中选择一条不会产生环路的具有最小耗费的边加入已选择的边的集合中。

注意到所选取的边若产生环路则不可能形成一棵生成树。

Kruskal算法分e步,其中e是网络中边的数目。

按耗费递增的顺序来考虑这e条边,每次考虑一条边。

当考虑某条边时,若将其加入到已选边的集合中会出现环路,则将其抛弃,否则,将它选入。

考察图1-12a中的网络。

初始时没有任何边被选择。

图13-12b显示了各节点的当前状态。

边(1,6)是最先选入的边,它被加入到欲构建的生成树中,得到图13-12c。

下一步选择边(3,4)并将其加入树中(如图13-12d所示)。

然后考虑边(2,7),将它加入树中并不会产生环路,于是便得到图13-12e。

下一步考虑边(2,3)并将其加入树中(如图13-12f所示)。

在其余还未考虑的边中,(7,4)具有最小耗费,因此先考虑它,将它加入正在创建的树中会产生环路,所以将其丢弃。

此后将边(5,4)加入树中,得到的树如图13-12g所示。

下一步考虑边(7,5),由于会产生环路,将其丢弃。

最后考虑边(6,5)并将其加入树中,产生了一棵生成树,其耗费为99。

图1-13给出了Kruskal算法的伪代码。

//在一个具有n个顶点的网络中找到一棵最小生成树令T为所选边的集合,初始化T=令E为网络中边的集合while(E≠)&&(|T|≠n-1){令(u,v)为E中代价最小的边E=E-{(u,v)}//从E中删除边if((u,v)加入T中不会产生环路)将(u,v)加入T}if(|T|==n-1)T是最小耗费生成树else网络不是互连的,不能找到生成树图13-13Kruskao算法的伪代码

(2)正确性证明利用前述装载问题所用的转化技术可以证明图13-13的贪婪算法总能建立一棵最小耗费生成树。

需要证明以下两点:

1)只要存在生成树,Kruskal算法总能产生一棵生成树;2)产生的生成树具有最小耗费。

令G为任意加权无向图(即G是一个无向网络)。

从12.11.3节可知当且仅当一个无向图连通时它有生成树。

而且在Kruskal算法中被拒绝(丢弃)的边是那些会产生环路的边。

删除连通图环路中的一条边所形成的图仍是连通图,因此如果G在开始时是连通的,则T与E中的边总能形成一个连通图。

也就是若G开始时是连通的,算法不会终止于E=和|T|<n-1。

现在来证明所建立的生成树T具有最小耗费。

由于G具有有限棵生成树,所以它至少具有一棵最小生成树。

令U为这样的一棵最小生成树,T与U都刚好有n-1条边。

如果T=U,则T就具有最小耗费,那么不必再证明下去。

因此假设T≠U,令k(k>0)为在T中而不在U中的边的个数,当然k也是在U中而不在T中的边的数目。

通过把U变换为T来证明U与T具有相同的耗费,这种转化可在k步内完成。

每一步使在T而不在U中的边的数目刚好减1。

而且U的耗费不会因为转化而改变。

经过k步的转化得到的U将与原来的U具有相同的耗费,且转化后U中的边就是T中的边。

由此可知,T具有最小耗费。

每步转化包括从T中移一条边e到U中,并从U中移出一条边f。

边e与f的选取按如下方式进行:

1)令e是在T中而不在U中的具有最小耗费的边。

由于k>0,这条边肯定存在。

2)当把e加入U时,则会形成唯一的一条环路。

令f为这条环路上不在T中的任意一条边。

由于T中不含环路,因此所形成的环路中至少有一条边不在T中。

从e与f的选择方法中可以看出,V=U+{e}-{f}是一棵生成树,且T中恰有k-1条边不在V中出现。

现在来证明V的耗费与U的相同。

显然,V的耗费等于U的耗费加上边e的耗费再减去边f的耗费。

若e的耗费比f的小,则生成树V的耗费比U的耗费小,这是不可能的。

如果e的耗费高于f,在Kruskal算法中f会在e之前被考虑。

由于f不在T中,Kruskal算法在考虑f能否加入T时已将f丢弃,因此f和T中耗费小于或等于f的边共同形成环路。

通过选择e,所有这些边均在U中,因此U肯定含有环路,但是实际上这不可能,因为U是一棵生成树。

e的代价高于f的假设将会导致矛盾。

剩下的唯一的可能是e与f具有相同的耗费,由此可知V与U的耗费相同。

(3)数据结构的选择及复杂性分析为了按耗费非递减的顺序选择边,可以建立最小堆并根据需要从堆中一条一条地取出各边。

当图中有e条边时,需花(e)的时间初始化堆及O(loge)的时间来选取每一条边。

边的集合T与G中的顶点一起定义了一个由至多n个连通子图构成的图。

用顶点集合来描述每个子图,这些顶点集合没有公共顶点。

为了确定边(u,v)是否会产生环路,仅需检查u,v是否在同一个顶点集中(即处于同一子图)。

如果是,则会形成一个环路;如果不是,则不会产生环路。

因此对于顶点集使用两个Find操作就足够了。

当一条边包含在T中时,2个子图将被合并成一个子图,即对两个集合执行Union操作。

集合的Find和Union操作可利用8.10.2节的树(以及加权规则和路径压缩)来高效地执行。

Find操作的次数最多为2e,Union操作的次数最多为n-1(若网络是连通的,则刚好是n-1次)。

加上树的初始化时间,算法中这部分的复杂性只比O(n+e)稍大一点。

对集合T所执行的唯一操作是向T中添加一条新边。

T可用数组t来实现。

添加操作在数组的一端进行,因为最多可在T中加入n-1条边,因此对T的操作总时间为O(n)。

总结上述各个部分的执行时间,可得图13-13算法的渐进复杂性为O(n+eloge)。

(4)实现利用上述数据结构,图1-13可用C++代码来实现。

首先定义EdgeNode类(见程序13-6),它是最小堆的元素及生成树数组t的数据类型----------------------------------------------

plot(100+t+15*cos(3.05*t),t=0..200,coords=polar,axes=none,scaling=constrained);2004-5-2719:

42:

37

b等级:

职业侠客文章:

470积分:

956门派:

黑客帝国注册:

2003-8-28第12楼

程序13-6Kruskal算法所需要的数据类型template<classT>classEdgeNode{public:

operatorT()const{returnweight;}private:

Tweight;//边的高度intu,v;//边的端点};为了更简单地使用8.10.2节的查找和合并策略,定义了UnionFind类,它的构造函数是程序8-16的初始化函数,Union是程序8-16的加权合并函数,Find是程序8-17的路径压缩搜索函数。

为了编写与网络描述无关的代码,还定义了一个新的类UNetWork,它包含了应用于无向网络的所有函数。

这个类与Undirected类的差别在于Undirected类中的函数不要求加权边,而UNetWork要求边必须带有权值。

UNetWork中的成员需要利用Network类中定义的诸如Begin和NextVertex的遍历函数。

不过,新的遍历函数不仅需要返回下一个邻接的顶点,而且要返回到达这个顶点的边的权值。

这些遍历函数以及有向和无向加权网络的其他函数一起构成了WNetwork类(见程序13-7)。

程序13-7WNetwork类template<classT>classWNetwork:

virtualpublicNetwork{public:

virtualvoidFirst(inti,int&j,T&c)=0;virtualvoidNext(inti,int&j,T&c)=0;};象Begin和NextVertex一样,可在AdjacencyWDigraph及LinkedWDigraph类中加入函数First与Next。

现在AdjacencyWDigraph及LinkedWDigraph类都需要从WNetWork中派生而来。

由于AdjacencyWGraph类和LinkedWGraph类需要访问UNetwork的成员,所以这两个类还必须从UNetWork中派生而来。

UNetWork:

:

Kruskal的代码见程序13-8,它要求将Edges()定义为NetWork类的虚成员,并且把UNetWork定义为EdgeNode的友元)。

如果没有生成树,函数返回false,否则返回true。

注意当返回true时,在数组t中返回最小耗费生成树。

程序13-8Kruskal算法的C++代码template<classT>boolUNetwork<T>:

:

Kruskal(EdgeNode<T>t[]){//使用Kruskal算法寻找最小耗费生成树//如果不连通则返回false//如果连通,则在t[0:

n-2]中返回最小生成树intn=Vertices();inte=Edges();//设置网络边的数组InitializePos();//图遍历器EdgeNode<T>*E=newEdgeNode<T>[e+1];intk=0;//E的游标for(inti=1;i<=n;i++){//使所有边附属于iintj;Tc;First(i,j,c);while(j){//j邻接自iif(i<j){//添加到达E的边E[++k].weight=c;E[k].u=i;E[k].v=j;}Next(i,j,c);}}//把边放入最小堆MinHeap<EdgeNode<T>>H

(1);H.Initialize(E,e,e);UnionFindU(n);//合并/搜索结构//根据耗费的次序来抽取边k=0;//此时作为t的游标while(e&&k<n-1){//生成树未完成,尚有剩余边EdgeNode<T>x;H.DeleteMin(x);//最小耗费边e--;inta=U.Find(x.u);intb=U.Find(x.v);if(a!

=b){//选择边t[k++]=x;U.Union(a,b);}}DeactivatePos();H.Deactivate();return(k==n-1);}2.Prim算法与Kruskal算法类似,Prim算法通过每次选择多条边来创建最小生成树。

选择下一条边的贪婪准则是:

从剩余的边中,选择一条耗费最小的边,并且它的加入应使所有入选的边仍是一棵树。

最终,在所有步骤中选择的边形成一棵树。

相反,在Kruskal算法中所有入选的边集合最终形成一个森林。

Prim算法从具有一个单一顶点的树T开始,这个顶点可以是原图中任意一个顶点。

然后往T中加入一条代价最小的边(u,v)使T&Egrave;{(u,v)}仍是一棵树,这种加边的步骤反复循环直到T中包含n-1条边。

注意对于边(u,v),u、v中正好有一个顶点位于T中。

Prim算法的伪代码如图1-14所示。

在伪代码中也包含了所输入的图不是连通图的可能,在这种情况下没有生成树。

图1-15显示了对图1-12a使用Prim算法的过程。

把图1-14的伪代码细化为C++程序及其正确性的证明留作练习(练习31)。

//假设网络中至少具有一个顶点设T为所选择的边的集合,初始化T=设TV为已在树中的顶点的集合,置TV={1}令E为网络中边的集合while(E<>)&&(|T|<>n-1){令(u,v)为最小代价边,其中uTV,vTVif(没有这种边)breakE=E-{(u,v)}//从E中删除此边在T中加入边(u,v)}if(|T|==n-1)T是一棵最小生成树else网络是不连通的,没有最小生成树图13-14Prim最小生成树算法如果根据每个不在TV中的顶点v选择一个顶点near(v),使得near(v)&Icirc;TV且cost(v,near(v))的值是所有这样的near(v)节点中最小的,则实现Prim算法的时间复杂性为O(n2)。

下一条添加到T中的边是这样的边:

其cost(v,near(v))最小,且vTV。

3.Sollin算法Sollin算法每步选择若干条边。

在每步开始时,所选择的边及图中的n个顶点形成一个生成树的森林。

在每一步中为森林中的每棵树选择一条边,这条边刚好有一个顶点在树中且边的代价最小。

将所选择的边加入要创建的生成树中。

注意一个森林中的两棵树可选择同一条边,因此必须多次复制同一条边。

当有多条边具有相同的耗费时,两棵树可选择与它们相连的不同的边,在这种情况下,必须丢弃其中的一条边。

开始时,所选择的边的集合为空。

若某一步结束时仅剩下一棵树或没有剩余的边可供选择时算法终止。

图1-6给出了初始状态为图1-12a时,使用Sollin算法的步骤。

初始入选边数为0时的情形如图13-12a时,森林中的每棵树均是单个顶点。

顶点1,2,.,7所选择的边分别是(1.6),(2,7),(3,4),(4,3),(5,4),(6,1),(7,2),其中不同的边是(1,6),(2,7),(3,4)和(5,4),将这些边加入入选边的集合后所得到的结果如图13-16a所示。

下一步具有顶点集{1,6}的树选择边(6,5),剩下的两棵树选择边(2,3),加入这两条边后已形成一棵生成树,构建好的生成树见图13-6b。

Sollin算法的C++程序实现及其正确性证明留作练习(练习32)。

----------------------------------------------

plot(100+t+15*cos(3.05*t),t=0..200,coords=polar,axes=none,scaling=constrained);

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工作范文 > 行政公文

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1