数模论坛

 找回密码
 注-册-帐-号
搜索
热搜: 活动 交友 discuz
楼主: b

贪婪算法

[复制链接]
b
 楼主| 发表于 2004-5-28 03:42:37 | 显示全部楼层
< left; mso-layout-grid-align: none" align=left>1.3.6 最小耗费生成树<p></p></P>< 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>在例1 - 2及1 - 3中已考察过这个问题。因为具有<I>n </I>个顶点的无向网络<I>G</I>的每个生成树刚好具有<I>n</I>-1条边,所以问题是用某种方法选择<I>n</I>-1条边使它们形成<I>G</I>的最小生成树。至少可以采用三种不同的贪婪策略来选择这<I>n</I>-1条边。这三种求解最小生成树的贪婪算法策略是: K r u s k a l算法,P r i m算法和S o l l i n算法。<p></p></P>< 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>1. Kruskal算法<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>(1) 算法思想<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>K r u s k a l算法每次选择<I>n</I>- 1条边,所使用的贪婪准则是:从剩下的边中选择一条不会产生环路的具有最小耗费的边加入已选择的边的集合中。注意到所选取的边若产生环路则不可能形成一棵生成树。K r u s k a l算法分<I>e </I>步,其中<I>e </I>是网络中边的数目。按耗费递增的顺序来考虑这<I>e </I>条边,每次考虑一条边。当考虑某条边时,若将其加入到已选边的集合中会出现环路,则将其抛弃,否则,将它选入。<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>考察图1-12a 中的网络。初始时没有任何边被选择。图13-12b 显示了各节点的当前状态。边( 1 , 6)是最先选入的边,它被加入到欲构建的生成树中,得到图1 3 - 1 2 c。下一步选择边( 3,4)并将其加入树中(如图1 3 - 1 2 d所示)。然后考虑边( 2,7 ),将它加入树中并不会产生环路,于是便得到图1 3 - 1 2 e。下一步考虑边( 2,3)并将其加入树中(如图1 3 - 1 2 f所示)。在其余还未考虑的边中,(7,4)具有最小耗费,因此先考虑它,将它加入正在创建的树中会产生环路,所以将其丢弃。此后将边( 5,4)加入树中,得到的树如图13-12g 所示。下一步考虑边( 7,5),由于会产生环路,将其丢弃。最后考虑边( 6,5)并将其加入树中,产生了一棵生成树,其耗费为9 9。图1 - 1 3给出了K r u s k a l算法的伪代码。<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left> <p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left>/ /在一个具有<I>n </I>个顶点的网络中找到一棵最小生成树<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left>令<I>T</I>为所选边的集合,初始化<I>T</I>=<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left>令E 为网络中边的集合<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left><I>w h i l e</I>(<I>E</I>≠ )&amp;&amp;(| <I>T </I>|≠<I>n</I>- 1 ) {<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left>令(<I>u</I>,<I>v</I>)为<I>E</I>中代价最小的边<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left><I>E</I>=<I>E</I>- { (<I>u</I>,<I>v</I>) } / /从<I>E</I>中删除边<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left><I>i f</I>( (<I>u</I>,<I>v</I>)加入<I>T</I>中不会产生环路)将( <I>u</I>,<I>v</I>)加入<I>T<p></p></I></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left>}<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left><I>i f</I>(| <I>T </I>| = =<I>n</I>-1) <I>T</I>是最小耗费生成树<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left><I>e l s e </I>网络不是互连的,不能找到生成树<p></p></P><P 27pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 9.0pt; mso-layout-grid-align: none" align=left>图13-13 Kruskao算法的伪代码<p></p></P><P left; mso-layout-grid-align: none" align=left> <p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>(2) 正确性证明<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>利用前述装载问题所用的转化技术可以证明图1 3 - 1 3的贪婪算法总能建立一棵最小耗费生成树。需要证明以下两点: 1) 只要存在生成树,K r u s k a l算法总能产生一棵生成树; 2) 产生的生成树具有最小耗费。令<I>G</I>为任意加权无向图(即<I>G</I>是一个无向网络)。从1 2 . 11 . 3节可知当且仅当一个无向图连通时它有生成树。而且在Kruskal 算法中被拒绝(丢弃)的边是那些会产生环路的边。删除连通图环路中的一条边所形成的图仍是连通图,因此如果<I>G</I>在开始时是连通的,则<I>T</I>与<I>E</I>中的边总能形成一个连通图。也就是若<I>G</I>开始时是连通的,算法不会终止于<I>E</I>= 和| <I>T </I>|&lt; <I>n</I>- 1。<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>现在来证明所建立的生成树<I>T</I>具有最小耗费。由于<I>G</I>具有有限棵生成树,所以它至少具有一棵最小生成树。令<I>U</I>为这样的一棵最小生成树, <I>T</I>与<I>U</I>都刚好有<I>n</I>- 1条边。如果<I>T</I>=<I>U</I>, 则<I>T</I>就具有最小耗费,那么不必再证明下去。因此假设<I>T</I>≠<I>U</I>,令<I>k</I>(<I>k </I>&gt;0) 为在<I>T</I>中而不在<I>U</I>中的边的个数,当然k 也是在<I>U</I>中而不在<I>T</I>中的边的数目。<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>通过把<I>U</I>变换为<I>T</I>来证明<I>U</I>与<I>T</I>具有相同的耗费,这种转化可在<I>k </I>步内完成。每一步使在<I>T</I>而不在<I>U</I>中的边的数目刚好减1。而且<I>U</I>的耗费不会因为转化而改变。经过<I>k </I>步的转化得到的<I>U</I>将与原来的<I>U</I>具有相同的耗费,且转化后<I>U</I>中的边就是<I>T</I>中的边。由此可知, <I>T</I>具有最小耗费。每步转化包括从<I>T</I>中移一条边<I>e </I>到<I>U</I>中,并从<I>U</I>中移出一条边<I>f</I>。边<I>e </I>与<I>f </I>的选取按如下方式进行:<p></p></P><P left; mso-layout-grid-align: none" align=left>1) 令<I>e </I>是在<I>T</I>中而不在<I>U</I>中的具有最小耗费的边。由于<I>k </I>&gt;0,这条边肯定存在。<p></p></P><P left; mso-layout-grid-align: none" align=left>2) 当把<I>e </I>加入<I>U</I>时,则会形成唯一的一条环路。令<I>f </I>为这条环路上不在<I>T</I>中的任意一条边。<p></p></P><P left; mso-layout-grid-align: none" align=left>由于T中不含环路,因此所形成的环路中至少有一条边不在<I>T</I>中。<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>从<I>e </I>与<I>f </I>的选择方法中可以看出, <I>V</I>=<I>U</I>+ {<I>e</I>} -{ <I>f </I>} 是一棵生成树,且<I>T</I>中恰有<I>k</I>- 1条边不在<I>V</I>中出现。现在来证明<I>V</I>的耗费与<I>U</I>的相同。显然,<I>V</I>的耗费等于<I>U</I>的耗费加上边<I>e </I>的耗费再减去边<I>f </I>的耗费。若<I>e </I>的耗费比<I>f </I>的小,则生成树<I>V</I>的耗费比<I>U</I>的耗费小,这是不可能的。如果<I>e </I>的耗费高于<I>f</I>,在K r u s k a l算法中<I>f </I>会在<I>e </I>之前被考虑。由于<I>f </I>不在<I>T</I>中,Kruskal 算法在考虑<I>f </I>能否加入<I>T</I>时已将<I>f </I>丢弃,因此<I>f </I>和<I>T</I>中耗费小于或等于<I>f </I>的边共同形成环路。通过选择<I>e</I>,所有这些边均在<I>U</I>中,因此<I>U</I>肯定含有环路,但是实际上这不可能,因为<I>U</I>是一棵生成树。<I>e </I>的代价高于<I>f </I>的假设将会导致矛盾。剩下的唯一的可能是<I>e </I>与<I>f </I>具有相同的耗费,由此可知<I>V</I>与<I>U</I>的耗费相同。<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>(3) 数据结构的选择及复杂性分析<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>为了按耗费非递减的顺序选择边,可以建立最小堆并根据需要从堆中一条一条地取出各边。当图中有<I>e </I>条边时,需花(<I>e</I>) 的时间初始化堆及O ( l o g<I>e</I>) 的时间来选取每一条边。边的集合<I>T</I>与<I>G</I>中的顶点一起定义了一个由至多<I>n </I>个连通子图构成的图。用顶点集合来描述每个子图,这些顶点集合没有公共顶点。为了确定边( <I>u</I>,<I>v</I>)是否会产生环路,仅需检查<I>u</I>,<I>v </I>是否在同一个顶点集中(即处于同一子图)。如果是,则会形成一个环路;如果不是,则不会产生环路。因此对于顶点集使用两个F i n d操作就足够了。当一条边包含在<I>T</I>中时,2个子图将被合并成一个子图,即对两个集合执行U n i o n操作。集合的F i n d和U n i o n操作可利用8 . 1 0 . 2节的树(以及加权规则和路径压缩)来高效地执行。F i n d操作的次数最多为2<I>e</I>,Un i o n操作的次数最多为<I>n</I>- 1(若网络是连通的,则刚好是<I>n</I>- 1次)。加上树的初始化时间,算法中这部分的复杂性只比O (<I>n</I>+<I>e</I>) 稍大一点。<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>对集合<I>T</I>所执行的唯一操作是向<I>T</I>中添加一条新边。<I>T</I>可用数组<I>t </I>来实现。添加操作在数组<p></p></P><P left; mso-layout-grid-align: none" align=left>的一端进行,因为最多可在<I>T</I>中加入<I>n</I>- 1条边,因此对<I>T</I>的操作总时间为O (<I>n</I>)。<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>总结上述各个部分的执行时间,可得图1 3 - 1 3算法的渐进复杂性为O (<I>n</I>+<I>e</I>l o g<I>e</I>)。<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>(4) 实现<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>利用上述数据结构,图1 - 1 3可用C + +代码来实现。首先定义E d g e N o d e类(见程序1 3 - 6 ),它是最小堆的元素及生成树数组<I>t </I>的数据类型</P>
b
 楼主| 发表于 2004-5-28 03:42:54 | 显示全部楼层
< left; mso-layout-grid-align: none" align=left>程序13-6 Kruskal算法所需要的数据类型<p></p></P>< left; mso-layout-grid-align: none" align=left>template &lt;class T&gt;<p></p></P>< left; mso-layout-grid-align: none" align=left>class EdgeNode {<p></p></P><P left; mso-layout-grid-align: none" align=left>p u b l i c :<p></p></P><P left; mso-layout-grid-align: none" align=left>operator T() const {return weight;}<p></p></P><P left; mso-layout-grid-align: none" align=left>p r i v a t e :<p></p></P><P left; mso-layout-grid-align: none" align=left>T weight;//边的高度<p></p></P><P left; mso-layout-grid-align: none" align=left>int u, v;//边的端点<p></p></P><P left; mso-layout-grid-align: none" align=left>} ;<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>为了更简单地使用8 . 1 0 . 2节的查找和合并策略,定义了U n i o n F i n d类,它的构造函数是程序8 - 1 6的初始化函数,U n i o n是程序8 - 1 6的加权合并函数,F i n d是程序8 - 1 7的路径压缩搜索函数。<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>为了编写与网络描述无关的代码,还定义了一个新的类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类(见程序1 3 - 7)。<p></p></P><P left; mso-layout-grid-align: none" align=left>程序13-7 WNetwork类<p></p></P><P left; mso-layout-grid-align: none" align=left>template&lt;class T&gt;<p></p></P><P left; mso-layout-grid-align: none" align=left>class WNetwork : virtual public Network<p></p></P><P left; mso-layout-grid-align: none" align=left>{<p></p></P><P left; mso-layout-grid-align: none" align=left>public :<p></p></P><P left; mso-layout-grid-align: none" align=left>virtual void First(int i, int&amp; j, T&amp; c)=0;<p></p></P><P left; mso-layout-grid-align: none" align=left>virtual void Next(int i, int&amp; j, T&amp; c)=0;<p></p></P><P left; mso-layout-grid-align: none" align=left>} ;<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>象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的代码见程序1 3 - 8,它要求将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 中返回最小耗费生成树。<p></p></P><P left; mso-layout-grid-align: none" align=left>程序13-8 Kr u s k a l算法的C + +代码<p></p></P><P left; mso-layout-grid-align: none" align=left>template&lt;class T&gt;<p></p></P><P left; mso-layout-grid-align: none" align=left>bool UNetwork&lt;T&gt;::Kruskal(EdgeNode&lt;T&gt; t[])<p></p></P><P left; mso-layout-grid-align: none" align=left>{// 使用K r u s k a l算法寻找最小耗费生成树<p></p></P><P left; mso-layout-grid-align: none" align=left>// 如果不连通则返回false<p></p></P><P left; mso-layout-grid-align: none" align=left>// 如果连通,则在t [ 0 : n - 2 ]中返回最小生成树<p></p></P><P left; mso-layout-grid-align: none" align=left>int n = Ve r t i c e s ( ) ;<p></p></P><P left; mso-layout-grid-align: none" align=left>int e = Edges();<p></p></P><P left; mso-layout-grid-align: none" align=left>/ /设置网络边的数组<p></p></P><P left; mso-layout-grid-align: none" align=left>InitializePos(); // 图遍历器<p></p></P><P left; mso-layout-grid-align: none" align=left>EdgeNode&lt;T&gt; *E = new EdgeNode&lt;T&gt; [e+1];<p></p></P><P left; mso-layout-grid-align: none" align=left>int k = 0; // E的游标<p></p></P><P left; mso-layout-grid-align: none" align=left>for (int i = 1; i &lt;= n; i++) { // 使所有边附属于i<p></p></P><P left; mso-layout-grid-align: none" align=left>int j;<p></p></P><P left; mso-layout-grid-align: none" align=left>T c;<p></p></P><P left; mso-layout-grid-align: none" align=left>First(i, j, c);<p></p></P><P left; mso-layout-grid-align: none" align=left>while (j) { // j 邻接自i<p></p></P><P left; mso-layout-grid-align: none" align=left>if (i &lt; j) {// 添加到达E的边<p></p></P><P left; mso-layout-grid-align: none" align=left>E[++k].weight = c;<p></p></P><P left; mso-layout-grid-align: none" align=left>E[k].u = i;<p></p></P><P left; mso-layout-grid-align: none" align=left>E[k].v = j;}<p></p></P><P left; mso-layout-grid-align: none" align=left>Next(i, j, c);<p></p></P><P left; mso-layout-grid-align: none" align=left>}<p></p></P><P left; mso-layout-grid-align: none" align=left>}<p></p></P><P left; mso-layout-grid-align: none" align=left>// 把边放入最小堆<p></p></P><P left; mso-layout-grid-align: none" align=left>MinHeap&lt;EdgeNode&lt;T&gt; &gt; H(1);<p></p></P><P left; mso-layout-grid-align: none" align=left>H.Initialize(E, e, e);<p></p></P><P left; mso-layout-grid-align: none" align=left>UnionFind U(n); // 合并/搜索结构<p></p></P><P left; mso-layout-grid-align: none" align=left>// 根据耗费的次序来抽取边<p></p></P><P left; mso-layout-grid-align: none" align=left>k = 0; // 此时作为t的游标<p></p></P><P left; mso-layout-grid-align: none" align=left>while (e &amp;&amp; k &lt; n - 1) {<p></p></P><P left; mso-layout-grid-align: none" align=left>// 生成树未完成,尚有剩余边<p></p></P><P left; mso-layout-grid-align: none" align=left>EdgeNode&lt;T&gt; x;<p></p></P><P left; mso-layout-grid-align: none" align=left>H.DeleteMin(x); // 最小耗费边<p></p></P><P left; mso-layout-grid-align: none" align=left>e - - ;<p></p></P><P left; mso-layout-grid-align: none" align=left>int a = U.Find(x.u);<p></p></P><P left; mso-layout-grid-align: none" align=left>int b = U.Find(x.v);<p></p></P><P left; mso-layout-grid-align: none" align=left>if (a != b) {// 选择边<p></p></P><P left; mso-layout-grid-align: none" align=left>t[k++] = x;<p></p></P><P left; mso-layout-grid-align: none" align=left>U . U n i o n ( a , b ) ; }<p></p></P><P left; mso-layout-grid-align: none" align=left>}<p></p></P><P left; mso-layout-grid-align: none" align=left>D e a c t i v a t e P o s ( ) ;<p></p></P><P left; mso-layout-grid-align: none" align=left>H . D e a c t i v a t e ( ) ;<p></p></P><P left; mso-layout-grid-align: none" align=left>return (k == n - 1);<p></p></P><P left; mso-layout-grid-align: none" align=left>}<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>2. Prim算法<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>与Kr u s k a l算法类似,P r i m算法通过每次选择多条边来创建最小生成树。选择下一条边的贪婪准则是:从剩余的边中,选择一条耗费最小的边,并且它的加入应使所有入选的边仍是一棵树。最终,在所有步骤中选择的边形成一棵树。相反,在Kruskal 算法中所有入选的边集合最终形成一个森林。<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>P r i m算法从具有一个单一顶点的树<I>T</I>开始,这个顶点可以是原图中任意一个顶点。然后往<I>T</I>中加入一条代价最小的边( <I>u , v</I>)使<I>T</I>&Egrave;{ (<I>u , v</I>) }仍是一棵树,这种加边的步骤反复循环直到<I>T</I>中包含<I>n</I>- 1条边。注意对于边( <I>u , v</I>),<I>u</I>、<I>v </I>中正好有一个顶点位于<I>T</I>中。P r i m算法的伪代码如图1 -1 4所示。在伪代码中也包含了所输入的图不是连通图的可能,在这种情况下没有生成树。图1 - 1 5显示了对图1-12a 使用P r i m算法的过程。把图1 - 1 4的伪代码细化为C + +程序及其正确性的证明留作练习(练习3 1)。<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left> <p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left>/ /假设网络中至少具有一个顶点<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left>设<I>T</I>为所选择的边的集合,初始化<I>T</I>=<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left>设<I>T V</I>为已在树中的顶点的集合,置<I>T V</I>= { 1 }<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left>令<I>E</I>为网络中边的集合<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left><I>w h i l e</I>(<I>E</I>&lt; &gt; ) &amp; &amp; (| <I>T </I>| &lt; &gt; <I>n</I>-1) {<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left>令(<I>u , v</I>)为最小代价边,其中<I>u T V</I>, <I>v T V<p></p></I></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left><I>i f</I>(没有这种边) <I>b re a k<p></p></I></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left><I>E</I>=<I>E</I>- { (<I>u</I>,<I>v</I>) } / /从<I>E</I>中删除此边<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left>在<I>T</I>中加入边( <I>u , v</I>)<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left>}<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left><I>if </I>(| <I>T </I>| = =<I>n</I>- 1 ) <I>T</I>是一棵最小生成树<p></p></P><P 24pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 8.0pt; mso-layout-grid-align: none" align=left><I>else </I>网络是不连通的,没有最小生成树<p></p></P><P 27pt; TEXT-ALIGN: left; mso-char-indent-count: 3.0; mso-char-indent-size: 9.0pt; mso-layout-grid-align: none" align=left>图13-14 Prim最小生成树算法<p></p></P><P left; mso-layout-grid-align: none" align=left> <p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>如果根据每个不在<I>T V</I>中的顶点<I>v </I>选择一个顶点<I>n e ar </I>(<I>v</I>),使得<I>n e ar </I>(<I>v</I>) &Icirc; <I>TV </I>且<I>c o st </I>(<I>v</I>, <I>n e ar </I>(<I>v</I>) )的值是所有这样的<I>n e ar </I>(<I>v</I>) 节点中最小的,则实现P r i m算法的时间复杂性为O (<I>n</I>2 )。下一条添加到<I>T</I>中的边是这样的边:其<I>cost </I>(<I>v, near </I>(<I>v</I>)) 最小,且<I>v T V</I>。<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>3. Sollin算法<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>S o l l i n算法每步选择若干条边。在每步开始时,所选择的边及图中的<I>n</I>个顶点形成一个生成树的森林。在每一步中为森林中的每棵树选择一条边,这条边刚好有一个顶点在树中且边的代价最小。将所选择的边加入要创建的生成树中。注意一个森林中的两棵树可选择同一条边,因此必须多次复制同一条边。当有多条边具有相同的耗费时,两棵树可选择与它们相连的不同的边,在这种情况下,必须丢弃其中的一条边。开始时,所选择的边的集合为空。若某一步结束时仅剩下一棵树或没有剩余的边可供选择时算法终止。<p></p></P><P 20pt; TEXT-ALIGN: left; mso-char-indent-count: 2.0; mso-char-indent-size: 10.0pt; mso-layout-grid-align: none" align=left>图1 - 6给出了初始状态为图1-12a 时,使用S o l l i n算法的步骤。初始入选边数为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 ),将这些边加入入选边的集合后所得到的结果如图1 3 - 1 6 a所示。下一步具有顶点集{ 1 , 6 }的树选择边( 6 , 5 ),剩下的两棵树选择边( 2 , 3 ),加入这两条边后已形成一棵生成树,构建好的生成树见图1 3 - 6 b。S o l l i n算法的C + +程序实现及其正确性证明留作练习(练习3 2 )。<p></p></P>
b
 楼主| 发表于 2004-5-28 03:43:19 | 显示全部楼层
< left; mso-layout-grid-align: none" align=left>练习<p></p></P>< left; mso-layout-grid-align: none" align=left>8. 针对装载问题,扩充贪婪算法,考虑有两条船的情况,算法总能产生最优解吗?<p></p></P>< left; mso-layout-grid-align: none" align=left>9. 已知<I>n </I>个任务的执行序列。假设任务<I>i </I>需要<I>t</I><I>i </I>个时间单位。若任务完成的顺序为1,2,.,<I>n</I>,则任务<I>i </I>完成的时间为<I>c</I><I>i </I>=<I>i </I>&aring;<I>j </I>= 1<I>t</I><I>j </I>。任务的平均完成时间(Av e rge Completion Time, ACT)<p></p></P><P left; mso-layout-grid-align: none" align=left>为1<I>–n</I><I>n </I>&aring;<I>i </I>= 1<I>c</I><I>i </I>。<p></p></P><P left; mso-layout-grid-align: none" align=left>1) 考虑有四个任务的情况,每个任务所需时间分别是( 4,2,8,1)。若任务的顺序为1,2,3,4,则A C T是多少?<p></p></P><P left; mso-layout-grid-align: none" align=left>2) 若任务顺序为2,1,4,3,则A C T是多少?<p></p></P><P left; mso-layout-grid-align: none" align=left>3) 创建具有最小A C T的任务序列的贪婪算法分<I>n </I>步来构造该任务序列,在每一步中,从剩下的任务里选择时间最小的任务。对于1 ),利用这种策略获得的任务顺序为4,2,1,3,这种顺序的A C T是多少?<p></p></P><P left; mso-layout-grid-align: none" align=left>4) 写一个C + +程序实现3) 中的贪婪策略,程序的复杂性应为O (<I>n</I>l o g<I>n</I>),试证明之。<p></p></P><P left; mso-layout-grid-align: none" align=left>5) 证明利用3) 中的贪婪算法获得的任务顺序具有最小的A C T。<p></p></P><P left; mso-layout-grid-align: none" align=left>10. 若有两个工人执行练习9中的<I>n</I>个任务,需将任务分配给他们,同时他们具有自己的任务执行顺序。任务完成时间及A C T的定义同练习9。使A C T最小化的一种可行的贪婪算法是:两个工人轮流选择任务,每次从剩余的任务中选择时间最小的任务。每个人按照自己所选任务的顺序执行任务。对于练习9中的例子,假定工人1首先选择任务4,然后工人2选择任务2,工人1选择任务1,最后工人2选择任务3。<p></p></P><P left; mso-layout-grid-align: none" align=left>1) 利用C + +程序实现这种策略,其时间复杂性为多少?<p></p></P><P left; mso-layout-grid-align: none" align=left>2) 上述的贪婪策略总能获得最小的A C T吗?证明结论。<p></p></P><P left; mso-layout-grid-align: none" align=left>11. 1) 考虑有<I>m </I>个人可以执行任务,扩充练习1 0中的贪婪算法。<p></p></P><P left; mso-layout-grid-align: none" align=left>2) 算法能保证获得最优解吗?证明结论。<p></p></P><P left; mso-layout-grid-align: none" align=left>3) 用C + +程序实现此算法,其复杂性是多少?<p></p></P><P left; mso-layout-grid-align: none" align=left>12. 考虑例4 - 4的堆栈折叠问题。<p></p></P><P left; mso-layout-grid-align: none" align=left>1) 设计一个贪婪算法,将堆栈折叠为最小数目的子堆栈,使得每个子堆栈的高度均不超过<I>H</I>。<p></p></P><P left; mso-layout-grid-align: none" align=left>2) 算法总能保证得到数目最少的子堆栈吗?证明结论。<p></p></P><P left; mso-layout-grid-align: none" align=left>3) 用C + +代码实现1) 的算法。<p></p></P><P left; mso-layout-grid-align: none" align=left>4) 代码的时间复杂性是多少?<p></p></P><P left; mso-layout-grid-align: none" align=left>13. 编写C + +程序实现0 / 1背包问题,使用如下启发式方法:按价值密度非递减的顺序打包。<p></p></P><P left; mso-layout-grid-align: none" align=left>14. 根据<I>k</I>= 1的性能受限启发式方法编写一个C + +程序来实现0 / 1背包问题。<p></p></P><P left; mso-layout-grid-align: none" align=left>15. 对于<I>k</I>= 1的情况证明用性能受限的启发式方法求解0 / 1背包问题会发生边界错误。<p></p></P><P left; mso-layout-grid-align: none" align=left>16. 根据<I>k</I>= 2的性能受限启发式方法编写一个C + +程序来实现0 / 1背包问题。<p></p></P><P left; mso-layout-grid-align: none" align=left>17. 考虑0≤<I>x</I><I>i </I>≤1而不是<I>x</I><I>i </I>&Icirc;{ 0 , 1 }的连续背包问题。一种可行的贪婪策略是:按价值密度非递减的顺序检查物品,若剩余容量能容下正在考察的物品,将其装入;否则,往背包中装入此物品的一部分。<p></p></P><P left; mso-layout-grid-align: none" align=left>1) 对于<I>n</I>=3, <I>w</I>=[100,10,10], <I>p</I>= [ 2 0 , 1 5 , 1 5 ]及<I>c</I>= 1 0 5 ,上述装入方法获得的结果是什么?<p></p></P><P left; mso-layout-grid-align: none" align=left>2) 证明这种贪婪算法总能获得最优解。<p></p></P><P left; mso-layout-grid-align: none" align=left>3) 用一个C + +程序实现此算法。<p></p></P><P left; mso-layout-grid-align: none" align=left>18. 例1 3 - 1的渴婴问题是练习1 7中连续背包问题的一般化,将练习1 7的贪婪算法用于渴婴问题,算法能保证总能得到最优解吗?证明结论。<p></p></P><P left; mso-layout-grid-align: none" align=left>19. 1) 证明当且仅当二分图没有覆盖时,图1 3 - 7的算法找不到覆盖。<p></p></P><P left; mso-layout-grid-align: none" align=left>2) 给出一个具有覆盖的二分图,使得图1 3 - 7的算法找不到最小覆盖。<p></p></P><P left; mso-layout-grid-align: none" align=left>20. 当第一步选择了顶点1时,给出图1 3 - 7的工作过程。<p></p></P><P left; mso-layout-grid-align: none" align=left>21. 对于二分图覆盖问题设计另外一种贪婪启发式方法,可使用如下贪婪准则:如果<I>B</I>中的某一个顶点仅被<I>A</I>中一个顶点覆盖,选择<I>A</I>中这个顶点;否则,从<I>A</I>中选择一个顶点,使得它所覆盖的未被覆盖的顶点数目最多。<p></p></P><P left; mso-layout-grid-align: none" align=left>1) 给出这种贪婪算法的伪代码。<p></p></P><P left; mso-layout-grid-align: none" align=left>2) 编写一个C + +函数作为U n d i r e c t e d类的成员来实现上述贪婪算法。<p></p></P><P left; mso-layout-grid-align: none" align=left>3) 函数的复杂性是多少?<p></p></P><P left; mso-layout-grid-align: none" align=left>4) 验证代码的正确性。<p></p></P><P left; mso-layout-grid-align: none" align=left>22. 令<I>G</I>为无向图,<I>S</I>为<I>G</I>中顶点的子集,当且仅当<I>S</I>中的任意两个顶点都有一条边相连时,<I>S</I>为完备子图(c l i q u e),完备子图的大小即<I>S</I>中的顶点数目。最大完备子图( maximum clique)即具有最大项点数目的完备子图。在图中寻找最大完备子图的问题(即最大完备子图问题)是一个N P-复杂问题。<p></p></P><P left; mso-layout-grid-align: none" align=left>1) 给出最大完备子图问题的一种可行的贪婪算法及其伪代码。<p></p></P><P left; mso-layout-grid-align: none" align=left>2) 给出一个能用1) 中的启发式算法求解最大完备子图的图例,以及不能用该算法求解的一个图例。<p></p></P><P left; mso-layout-grid-align: none" align=left>3) 将1) 中的启发式算法实现为Undirected::Clique(int C, int m) 共享成员,其中最大完备子图的大小返回到<I>m</I>中,最大完备子图的顶点返回到C中。<p></p></P><P left; mso-layout-grid-align: none" align=left>4) 代码的复杂性是多少?<p></p></P><P left; mso-layout-grid-align: none" align=left>23. 令<I>G</I>为一无向图,<I>S</I>为<I>G</I>中顶点的子集,当且仅当<I>S</I>中任意两个顶点都无边相连时, <I>S</I>为无关集(independent set)。最大无关集即是顶点数目最多的无关集。在一幅图中寻找最大无关集是一个N P-复杂问题。按练习2 2的要求解决最大无关集问题。<p></p></P><P left; mso-layout-grid-align: none" align=left>24. 对无向图<I>G</I>着色的方法是:为<I>G</I>中的顶点编号({ 1 , 2 ,.}),使得由一条边相连的两个顶点具有不同的编号。在图的着色问题中,要求利用最少的相互不同的颜色(编号)来给图<I>G</I>着色。图的着色问题也是一个N P-复杂问题。按练习2 2的要求解决图着色问题。<p></p></P><P left; mso-layout-grid-align: none" align=left>25. 证明当按路径长度的顺序产生一条最短路径时,所产生的下一条最短路径总是由已产生的一条最短路径扩充一条边得到。<p></p></P><P left; mso-layout-grid-align: none" align=left>26. 证明对于具有一条或多条具有负长度的边,图1 3 - 11的贪婪算法不一定能正确地计算出最短路径的长度。<p></p></P><P left; mso-layout-grid-align: none" align=left>27. 编写一个P a t h ( p , s , i )函数,利用函数S h o r t e s t P a t h s计算出的p 值,输出从顶点s到顶点i的一条最短路径。函数的复杂性是多少?<p></p></P><P left; mso-layout-grid-align: none" align=left>28. 若把有向图作为L i n k e d w D i g r a p h类的一个成员,重写程序1 3 - 5,函数应作为该类的一个成员。函数的复杂性是多少?<p></p></P><P left; mso-layout-grid-align: none" align=left>29. 若把有向图作为L i n k e d W D i g r a p h类的一个成员且仅有O (<I>n</I>)条边,重写程序1 3 - 5,<I>L</I>用最小堆来实现。函数的复杂性是多少?<p></p></P><P left; mso-layout-grid-align: none" align=left>30. 从N e t w o r k类(见程序1 2 - 1 5)派生出一个新的模板类D N e t w o r k(有向网络),这个类仅包含应用于有向网络的所有函数。为该类定义一个S h o r t e s t P a t h s函数,使得它与有向网络的描述形式无关,尤其适用于耗费邻接矩阵及邻接链表描述方法。在函数的实现过程中可利用原来的遍历函数,也可根据需要定义新的遍历函数。函数的复杂性应为O (<I>n</I>2 ),其中<I>n </I>是顶点的数目,试证明之。<p></p></P><P left; mso-layout-grid-align: none" align=left>*31. 1) 给出P r i m算法(如图1 3 - 1 4所示)的一种正确性证明。<p></p></P><P left; mso-layout-grid-align: none" align=left>2) 将图1 3 - 1 4细化为一个C + +程序U N e t w o r k : : P r i m,其复杂性应为O (<I>n</I>2 )。<p></p></P><P left; mso-layout-grid-align: none" align=left>3) 证明程序的复杂性确实是O(<I>n</I>2 )。<p></p></P><P left; mso-layout-grid-align: none" align=left>*32. 1) 证明对于任意连通无向图,S o l l i n算法总能找到一个最小耗费生成树。<p></p></P><P left; mso-layout-grid-align: none" align=left>2) 在S o l l i n算法中,最大的步骤数是多少?试用图中顶点数<I>n </I>来表示。<p></p></P><P left; mso-layout-grid-align: none" align=left>3) 编写一个C + +程序U N e t w o r k : : S o l l i n,使用S o l l i n算法找到一棵最小生成树。<p></p></P><P left; mso-layout-grid-align: none" align=left>4) 程序的复杂性是多少?<p></p></P><P left; mso-layout-grid-align: none" align=left>*33. 令T为一棵每条边均带有长度的树(不一定是二叉树)。令<I>S</I>为<I>T</I>中顶点的子集,并令<I>T / S</I>为从<I>T</I>中删除<I>S</I>中的顶点所得到的森林。我们希望能找到具有最小走势的子集<I>S</I>,使得<I>T / S</I>中没有从根到叶的距离大于<I>d</I>的森林。<p></p></P><P left; mso-layout-grid-align: none" align=left>1) 给出一种寻找最小走势子集<I>S</I>的贪婪算法(提示:从叶节点开始向根移动)。<p></p></P><P left; mso-layout-grid-align: none" align=left>2) 证明算法的正确性。<p></p></P><P left; mso-layout-grid-align: none" align=left>3) 算法的复杂性是多少?如果它不是<I>T</I>中顶点数的线性函数,则重新设计算法,使其复杂性是线性的。<p></p></P><P left; mso-layout-grid-align: none" align=left>*34. 令<I>T / S</I>表示将<I>S</I>中的每个顶点复制两份而获得的森林,其中父节点的指针指向一个复本,而另一复本的指针指向其儿子。针对这种情况再做练习3 3。<p></p></P><P left; mso-layout-grid-align: none" align=left> <p></p></P><P 10.5pt; mso-char-indent-count: 1.0; mso-char-indent-size: 10.5pt">( 说明:本资料是根据《数据结构、算法与应用》(美,Sartaj  Sahni著)一书第13-17章编辑、改写的。考虑到因特网传输速度等因素,大部分插图和公式不得不被删除。对于内容不连贯之处,请网友或读者参阅该书,敬请原谅。 )</P>
发表于 2004-6-5 18:49:29 | 显示全部楼层
<>怎么没人顶?</P><>我先来</P>[em01]
发表于 2004-6-6 02:12:35 | 显示全部楼层
<>虽然我还没看,但是还是要顶一顶</P>
发表于 2004-6-6 07:41:53 | 显示全部楼层
<>狂顶</P>
发表于 2004-6-7 05:49:31 | 显示全部楼层
<>不错不错,看你发了这么多,不顶不好意思啊,以后有时间再慢慢看吧</P>
发表于 2004-6-9 05:15:26 | 显示全部楼层
<>练习做完了是不是直接跟帖就行?</P>
发表于 2004-8-25 04:57:12 | 显示全部楼层
很好啊!
发表于 2004-8-25 17:29:56 | 显示全部楼层
好东东
您需要登录后才可以回帖 登录 | 注-册-帐-号

本版积分规则

小黑屋|手机版|Archiver|数学建模网 ( 湘ICP备11011602号 )

GMT+8, 2024-11-30 12:34 , Processed in 0.066145 second(s), 13 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表