< left; mso-layout-grid-align: none" align=left>程序13-6 Kruskal算法所需要的数据类型<p></p></P>< left; mso-layout-grid-align: none" align=left>template <class T><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<class T><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& j, T& c)=0;<p></p></P><P left; mso-layout-grid-align: none" align=left>virtual void Next(int i, int& j, T& 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<class T><p></p></P><P left; mso-layout-grid-align: none" align=left>bool UNetwork<T>::Kruskal(EdgeNode<T> 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<T> *E = new EdgeNode<T> [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 <= 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 < 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<EdgeNode<T> > 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 && k < 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<T> 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>È{ (<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>< > ) & & (| <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 , 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>) Î <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> |