⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 id5.txt

📁 介绍动态规划方法在解决背包问题、图象压缩、矩阵乘法链、最短路径、无交叉子集和元件折叠等方面的应用。
💻 TXT
📖 第 1 页 / 共 5 页
字号:
j = NextVe r t e x ( v ) ; }

// 更新箱子

while (!S.IsEmpty()) {

S . D e l e t e ( k ) ;

Change[k] = false;

MoveBins(SizeOfB, New[k], k);}

}

else MaxBin--;

}

D e a c t i v a t e P o s ( ) ;

D e s t r o y B i n s ( ) ;

delete [] New;

delete [] Change;

delete [] Cov;

return (covered == SizeOfB);

}

程序1 3 - 4首先计算出集合A和B的大小、初始化必要的双向链表结构、创建三个数组、初始化图遍历器、并创建一个栈。然后将数组C o v和C h a n g e初始化为f a l s e,并将A中的顶点根据它们覆盖B中顶点的数目插入到相应的双向链表中。

为了构造覆盖,首先按SizeOfB 递减至1的顺序检查双向链表。当发现一个非空的表时,就将其第一个顶点v 加入到覆盖中,这种策略即为选择具有最大New 值的顶点。将所选择的顶点加入覆盖数组C并检查B中所有与它邻接的顶点。若顶点j 与v 邻接且还未被覆盖,则将C o v [ j ]置为t r u e,表示顶点j 现在已被覆盖,同时将已被覆盖的B中的顶点数目加1。由于j 是最近被覆盖的,所有A中与j 邻接的顶点的New 值减1。下一个while 循环降低这些New 值并将New 值被降低的顶点保存在一个栈中。当所有与顶点v邻接的顶点的Cov 值更新完毕后,N e w值反映了A中每个顶点所能覆盖的新的顶点数,然而A中的顶点由于New 值被更新,处于错误的双向链表中,下一个while 循环则将这些顶点移到正确的表中。

 

1.3.5 单源最短路径

在这个问题中,给出有向图G,它的每条边都有一个非负的长度(耗费) a [i ][ j ],路径的长度即为此路径所经过的边的长度之和。对于给定的源顶点s,需找出从它到图中其他任意顶点(称为目的)的最短路径。图13-10a 给出了一个具有五个顶点的有向图,各边上的数即为长度。假设源顶点s 为1,从顶点1出发的最短路径按路径长度顺序列在图13-10b 中,每条路径前面的数字为路径的长度。

利用E. Dijkstra发明的贪婪算法可以解决最短路径问题,它通过分步方法求出最短路径。每一步产生一个到达新的目的顶点的最短路径。下一步所能达到的目的顶点通过如下贪婪准则选取:在还未产生最短路径的顶点中,选择路径长度最短的目的顶点。也就是说, D i j k s t r a的方法按路径长度顺序产生最短路径。

首先最初产生从s 到它自身的路径,这条路径没有边,其长度为0。在贪婪算法的每一步中,产生下一个最短路径。一种方法是在目前已产生的最短路径中加入一条可行的最短的边,结果产生的新路径是原先产生的最短路径加上一条边。这种策略并不总是起作用。另一种方法是在目前产生的每一条最短路径中,考虑加入一条最短的边,再从所有这些边中先选择最短的,这种策略即是D i j k s t r a算法。

可以验证按长度顺序产生最短路径时,下一条最短路径总是由一条已产生的最短路径加上一条边形成。实际上,下一条最短路径总是由已产生的最短路径再扩充一条最短的边得到的,且这条路径所到达的顶点其最短路径还未产生。例如在图1 3 - 1 0中,b 中第二条路径是第一条路径扩充一条边形成的;第三条路径则是第二条路径扩充一条边;第四条路径是第一条路径扩充一条边;第五条路径是第三条路径扩充一条边。

通过上述观察可用一种简便的方法来存储最短路径。可以利用数组p,p [ i ]给出从s 到达i的路径中顶点i 前面的那个顶点。在本例中p [ 1 : 5 ] = [ 0 , 1 , 1 , 3 , 4 ]。从s 到顶点i 的路径可反向创建。从i 出发按p[i],p[p[i]],p[p[p[i]]], .的顺序,直到到达顶点s 或0。在本例中,如果从i = 5开始,则顶点序列为p[i]=4, p[4]=3, p[3]=1=s,因此路径为1 , 3 , 4 , 5。

为能方便地按长度递增的顺序产生最短路径,定义d [ i ]为在已产生的最短路径中加入一条最短边的长度,从而使得扩充的路径到达顶点i。最初,仅有从s 到s 的一条长度为0的路径,这时对于每个顶点i,d [ i ]等于a [ s ] [ i ](a 是有向图的长度邻接矩阵)。为产生下一条路径,需要选择还未产生最短路径的下一个节点,在这些节点中d值最小的即为下一条路径的终点。当获得一条新的最短路径后,由于新的最短路径可能会产生更小的d值,因此有些顶点的d值可能会发生变化。

综上所述,可以得到图1 3 - 11所示的伪代码, 1) 将与s 邻接的所有顶点的p 初始化为s,这个初始化用于记录当前可用的最好信息。也就是说,从s 到i 的最短路径,即是由s到它自身那条路径再扩充一条边得到。当找到更短的路径时, p [ i ]值将被更新。若产生了下一条最短路径,需要根据路径的扩充边来更新d 的值。

 

1) 初始化d[i ] =a[s] [i ](1≤i≤n),

对于邻接于s的所有顶点i,置p[i ] =s, 对于其余的顶点置p[i ] = 0;

对于p[i]≠0的所有顶点建立L表。

2) 若L为空,终止,否则转至3 )。

3) 从L中删除d值最小的顶点。

4) 对于与i 邻接的所有还未到达的顶点j,更新d[ j ]值为m i n{d[ j ], d[i ] +a[i ][ j ] };若d[ j ]发生了变化且j 还未

在L中,则置p[ j ] = 1,并将j 加入L,转至2。

图1 - 11 最短路径算法的描述

1. 数据结构的选择

我们需要为未到达的顶点列表L选择一个数据结构。从L中可以选出d 值最小的顶点。如果L用最小堆(见9 . 3节)来维护,则这种选取可在对数时间内完成。由于3) 的执行次数为O ( n ),所以所需时间为O ( n l o g n )。由于扩充一条边产生新的最短路径时,可能使未到达的顶点产生更小的d 值,所以在4) 中可能需要改变一些d 值。虽然算法中的减操作并不是标准的最小堆操作,但它能在对数时间内完成。由于执行减操作的总次数为: O(有向图中的边数)= O ( n2 ),因此执行减操作的总时间为O ( n2 l o g n )。

若L用无序的链表来维护,则3) 与4) 花费的时间为O ( n2 ),3) 的每次执行需O(|L | ) =O( n )的时间,每次减操作需( 1 )的时间(需要减去d[j] 的值,但链表不用改变)。利用无序链表将图1 - 11的伪代码细化为程序1 3 - 5,其中使用了C h a i n (见程序3 - 8 )和C h a i n I t e r a t o r类(见程序3 - 1 8)。

程序13-5 最短路径程序

template<class T>

void AdjacencyWDigraph<T>::ShortestPaths(int s, T d[], int p[])

{// 寻找从顶点s出发的最短路径, 在d中返回最短距离

// 在p中返回前继顶点

if (s < 1 || s > n) throw OutOfBounds();

Chain<int> L; // 路径可到达顶点的列表

ChainIterator<int> I;

// 初始化d, p, L

for (int i = 1; i <= n; i++){

d[i] = a[s][i];

if (d[i] == NoEdge) p[i] = 0;

else {p[i] = s;

L . I n s e r t ( 0 , i ) ; }

}

// 更新d, p

while (!L.IsEmpty()) {// 寻找具有最小d的顶点v

int *v = I.Initialize(L);

int *w = I.Next();

while (w) {

if (d[*w] < d[*v]) v = w;

w = I.Next();}

// 从L中删除通向顶点v的下一最短路径并更新d

int i = *v;

L . D e l e t e ( * v ) ;

for (int j = 1; j <= n; j++) {

if (a[i][j] != NoEdge && (!p[j] ||

d[j] > d[i] + a[i][j])) {

// 减小d [ j ]

d[j] = d[i] + a[i][j];

// 将j加入L

if (!p[j]) L.Insert(0,j);

p[j] = i;}

}

}

}

若N o E d g e足够大,使得没有最短路径的长度大于或等于N o E d g e,则最后一个for 循环的i f条件可简化为:if (d[j] > d[i] + a[i][j]))  NoEdge 的值应在能使d[j]+a[i][j] 不会产生溢出的范围内。

2. 复杂性分析

程序1 3 - 5的复杂性是O ( n2 ),任何最短路径算法必须至少对每条边检查一次,因为任何一条边都有可能在最短路径中。因此这种算法的最小可能时间为O ( e )。由于使用耗费邻接矩阵来描述图,仅决定哪条边在有向图中就需O ( n2 )的时间。因此,采用这种描述方法的算法需花费O ( n2 )的时间。不过程序1 3 - 5作了优化(常数因子级)。即使改变邻接表,也只会使最后一个f o r循环的总时间降为O ( e )(因为只有与i 邻接的顶点的d 值改变)。从L中选择及删除最小距离的顶点所需总时间仍然是O( n2 )。

 

1.3.6 最小耗费生成树

在例1 - 2及1 - 3中已考察过这个问题。因为具有n 个顶点的无向网络G的每个生成树刚好具有n-1条边,所以问题是用某种方法选择n-1条边使它们形成G的最小生成树。至少可以采用三种不同的贪婪策略来选择这n-1条边。这三种求解最小生成树的贪婪算法策略是: K r u s k a l算法,P r i m算法和S o l l i n算法。

1. Kruskal算法

(1) 算法思想

K r u s k a l算法每次选择n- 1条边,所使用的贪婪准则是:从剩下的边中选择一条不会产生环路的具有最小耗费的边加入已选择的边的集合中。注意到所选取的边若产生环路则不可能形成一棵生成树。K r u s k a l算法分e 步,其中e 是网络中边的数目。按耗费递增的顺序来考虑这e 条边,每次考虑一条边。当考虑某条边时,若将其加入到已选边的集合中会出现环路,则将其抛弃,否则,将它选入。

考察图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算法的伪代码。

 

/ /在一个具有n 个顶点的网络中找到一棵最小生成树

令T为所选边的集合,初始化T=

令E 为网络中边的集合

w h i l e(E≠ )&&(| T |≠n- 1 ) {

令(u,v)为E中代价最小的边

E=E- { (u,v) } / /从E中删除边

i f( (u,v)加入T中不会产生环路)将( u,v)加入T

}

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -