动态规划的定义
在现实生活中,有一类活动的过程,由于它的特殊性,可将过程分成若干个互相联系的阶段,在它的每一阶段都需要作出决策,从而使整个过程达到最好的活动效果。因此各个阶段决策的选取不能任意确定,它依赖于当前面临的状态,又影响以后的发展。当各个阶段决策确定后,就组成一个决策序列,因而也就确定了整个过程的一条活动路线。这种把一个问题看作是一个前后关联具有链状结构的多阶段过程(如图)就称为多阶段决策过程,这种问题称为多阶段决策问题。
在多阶段决策问题中,各个阶段采取的决策,一般来说是与时间有关的,决策依赖于当前状态,又随即引起状态的转移,一个决策序列就是在变化的状态中产生出来的,故有"动态"的含义,我们称这种解决多阶段决策最优化的过程为动态规划方法。
应指出,动态规划是考察求解多阶段决策问题的一种途径、一种方法,而不是一种特殊算法。不像线性规划那样,具有一个标准的数学表达式和明确定义的一组规划。因此我们在学习时,除了要对基本概念和方法正确理解外,必须具体问题具体分析处理,以丰富的想象力去建立模型,用创造性的技巧去求解。
动态规划的最优化原理
作为整个过程的最优策略具有这样的性质:即无论过去的状态和决策如何,对以前的决策所形成的状态而言,余下的诸决策必须构成最优策略。(无论过程的初始状态/初始决策是什么,其余决策活动必须相对于初始决策所产生的状态构成一个最优决策序列,才可能使整个决策活动构成最优决策序列。)
简单地说,一个整体过程的最优策略的子策略一定是最优策略。
利用这个原理,可以把多阶段决策问题的求解过程看成是一个连续的逆推过程。由后向前逐步推算。在求解时,各种状态前面的状态和决策,对后面的子问题,只不过相当于其初始条件而己,不影晌后面过程的最优策略。原理的证明可用反证法。在此把它略去。
动态规划的求解方法
是先把问题分成多个子问题(一般地每个子问题是互相关联和影响的),再依次研究逐个问题的决策。决策就是某个阶段的状态确定后,从该状态演变到下一阶段状态的选择。当全体子问题都解决时,整体问题也随之解决。
用枚举的方法从所有可能的决策序列中去选取最优决策序列可能是较费时的笨拙的方法,但利用最优性原理去找出递推关系,再找最优决策序列就可能使得枚举数量大大下降,这就是动态规划方法设计算法的主要思路。
动态规划问题的经典实例
一、问题的提出
首先,例举一个典型的且很直观的多阶段决策问题:
[例] 下图表示城市之间的交通路网,线段上的数字表示费用,单向通行由A->E。试用动态规划的最优化原理求出A->E的最省费用。
如图从A到E共分为4个阶段,即第一阶段从A到B,第二阶段从B到C,第三阶段从C到D,第四阶段从D到E。除起点A和终点E外,其它各点既是上一阶段的终点又是下一阶段的起点。例如从A到B的第一阶段中,A为起点,终点有B1,B2,B3三个,因而这时走的路线有三个选择,一是走到B1,一是走到B2,一是走到B3。若选择B2的决策,B2就是第一阶段在我们决策之下的结果,它既是第一阶段路线的终点,又是第二阶段路线的始点。在第二阶段,再从B2点出发,对于B2点就有一个可供选择的终点集合(C1,C2,C3);若选择由B2走至C2为第二阶段的决策,则C2就是第二阶段的终点,同时又是第三阶段的始点。同理递推下去,可看到各个阶段的决策不同,线路就不同。很明显,当某阶段的起点给定时,它直接影响着后面各阶段的行进路线和整个路线的长短,而后面各阶段的路线的发展不受这点以前各阶段的影响。故此问题的要求是:在各个阶段选取一个恰
当的决策,使由这些决策组成的一个决策序列所决定的一条路线,其总路程最短。如何解决这个问题呢?
二、用枚举法
把所有由A->E可能的每一条路线的距离算出来,然后互相比较,找出最短者,相应地得出了最短路线。
三、用动态规划法求解
决策过程:
(1)由目标状态E向前推,可以分成四个阶段,即四个子问题。如上图所示。
(2)策略:每个阶段到E的最省费用为本阶段的决策路径。
(3)D1,D2是第一次输人的结点。他们到E都只有一种费用,在D1框上面标5,D2框上面标2。目前无法定下,那一个点将在全程最优策略的路径上。第二阶段计算中,5,2都应分别参加计算。
(4)C1,C2,C3是第二次输入结点,他们到D1,D2各有两种费用。此时应计算C1,C2,C3分别到E的最少费用。
C1的决策路径是 min{(C1D1),(C1D2)}。计算结果是C1+D1+E,在C1框上面标为8。
同理C2的决策路径计算结果是C2+D2+E,在C2框上面标为7。
同理C3的决策路径计算结果是C3+D2+E,在C3框上面标为12。
此时也无法定下第一,二阶段的城市那二个将在整体的最优决策路径上。
(5)第三次输入结点为B1,B2,B3,而决策输出结点可能为C1,C2,C3。仿前计算可得Bl,B2,B3的决策路径为如下情况。
Bl:B
1C
1费用 12+8=20, 路径:B1+C1+D1+E
B2:B
2C
1费用 6+8=14, 路径:B2+C1+D1+E
B3:B
2C
2费用 12+7=19, 路径:B3+C2+D2+E
此时也无法定下第一,二,三阶段的城市那三个将在整体的最优决策路径上。
(6)第四次输入结点为A,决策输出结点可能为B1,B2,B3。同理可得决策路径为
A:AB2,费用5+14=19,路径 A+B2+C1+D1+E。
此时才正式确定每个子问题的结点中,那一个结点将在最优费用的路径上。19将结果显然这种计算方法,符合最优原理。子问题的决策中,只对同一城市(结点)比较优劣。而同一阶段的城市(结点)的优劣要由下一个阶段去决定。
四、小结及比较
动态规划的最优化原理是“作为整个过程的最优策略具有这样的性质:无论过去的状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。”
与穷举法相比,动态规划的方法有两个明显的优点:
(1)大大减少了计算量
(2)丰富了计算结果
从上例的求解结果中,我们不仅得到由A点出发到终点E的最短路线及最短距离,而且还得到了从所有各中间点到终点的最短路线及最短距离,这对许多实际问题来讲是很有用的。
动态规划的最优化概念是在一定条件下,我到一种途径,在对各阶段的效益经过按问题具体性质所确定的运算以后,使得全过程的总效益达到最优。
五、应用动态规划要注意
1.阶段的划分是关键,必须依据题意分析,寻求合理的划分阶段(子问题)方法。而每个子问题是一个比原问题简单得多的优化问题。而且每个子问题的求解中,均利用它的一个后部子问题的最优化结果,直到最后一个子问题所得最优解,它就是原问题的最优解。
2.变量太多,同样会使问题无法求解。
3.最优化原理应在子问题求解中体现。有些问题也允许顺推。
动态规划问题的数学描述
首先,例举一个典型的且很直观的多阶段决策问题:
[例] 下图表示城市之间的交通路网,线段上的数字表示费用,单向通行由A->E。试用动态规划的最优化原理求出A->E的最省费用。
如图从A到E共分为4个阶段,即第一阶段从A到B,第二阶段从B到C,第三阶段从C到D,第四阶段从D到E。除起点A和终点E外,其它各点既是上一阶段的终点又是下一阶段的起点。例如从A到B的第一阶段中,A为起点,终点有B1,B2,B3三个,因而这时走的路线有三个选择,一是走到B1,一是走到B2,一是走到B3。若选择B2的决策,B2就是第一阶段在我们决策之下的结果,它既是第一阶段路线的终点,又是第二阶段路线的始点。在第二阶段,再从B2点出发,对于B2点就有一个可供选择的终点集合(C1,C2,C3);若选择由B2走至C2为第二阶段的决策,则C2就是第二阶段的终点,同时又是第三阶段的始点。同理递推下去,可看到各个阶段的决策不同,线路就不同。很明显,当某阶段的起点给定时,它直接影响着后面各阶段的行进路线和整个路线的长短,而后面各阶段的路线的发展不受这点以前各阶段的影响。故此问题的要求是:在各个阶段选取一个恰
当的决策,使由这些决策组成的一个决策序列所决定的一条路线,其总路程最短。
动态规划为此类多阶段决策问题寻求了一种简便的方法。为了便于讨论,我们先引入动态规划问题的一些概念、术语和符号。
名词解释:
(1)决策和阶段
在对问题的处理中作出某种选择性的行动就是决策。例如在且点需选择下一步到B1还是到B2,这就是一次决策。一个实际问题可能要有多次决策或多个决策点,为此对整个问题,可按其特点划分成需要作出选择的若干轮次,这些轮次即称为阶段。如图中,从A到E共分为4个阶段,即第一阶段从A到B,第二阶段从B到C,第三阶段从C到D,第四阶段从D到E。
(2)状态和状态变量
某一阶段的出发位置称为状态。通常一个阶段包含若干状态。例如阶段3含有3种状态C1,C2,C3。状态通常可有一个变量来描述,用来描述状态的变量称为状态变量,记第K阶段的状态变量为Uk。例如U3={C1,C2,C3}。
(3)决策变量和允许决策集合
在每一个阶段中都需有一次决策,决策也可以用一个变量来描述,称这种变量为决策变量,一般用Xk表示第K阶段的决策变量。在实际问题中,决策变量的取值往往限制在某一个范围之内,此范围称为允许决策集合,用Sk表示第K阶段的允许决策集合。例如,S3={D1,D2},它表示第三阶段可有两种不同的决策。那么Xk与Sk
之间的关系是UK+1=Xk(Uk)。当第K阶段的状态确定之后,可能作出的决策范围还要受到这一状态的影响,这就是说,决策变量Xk是状态变量Uk的函数,记为Xk(Uk),简记为Xk。把Xk的取值范围记为Sk(Uk),显然有Xk(Uk)∈Sk(Uk)。
例如在图的第三阶段中,若从状态C1出发,就可作出二种不同的决策,其允许决策集合S3(C1)={D1,D2}。若选取的点是D2,则D2是状态C1在决策X3(C1)作用下的下个新的状态,记作X3(C1)=D2。
(4)策略和最优策略
所有阶段依次排列构成问题的全过程。全过程中各阶段决策变量Xk(Uk)所组成的有序总体称为策略。在实际问题中,可供选择的策略有一定的范围,该范围称为允许策略集合P。从P中找出最优效果的策略称为最优策略。
(5)状态转移方程
前一阶段的终点就是后一阶段的起点,前一阶段的决策变量就是后一阶段的状态变量,这种关系描述了由K阶段到K十1阶段状态的演变规律,称为状态转移方程。如上图的状态转移方程为UK+1=Xk(Uk)。
(6)动态规划的函数基本方程
为了帮助大家理解动态规划的基本思想,先说最短路线的一个重要特性:如果从A->B->C->D是A至D的最短路线,那么从B到D的最短路线必是B->C->D。更一般地说:如果最短路线在第K阶段通过Pk,则由点Pk出发到达终点的这条路线对于从Pk出发到达终点的所有可能选择的不同路线来说,必定也是最短路线。
这就引出了从终点逐段向始点方向寻找最短路线的一种方法:
若以Uk表示第K阶段的一个决策点,从终点开始,依逆向求出倒数第一阶段、倒数第二阶段、倒数第三阶段、……、倒数第N-1阶段中各点到达终点的最短路线。最终求出从起点到终点的最短路线。这就是动态规划的基本思想。
下面,我们按照动态规划的方法将上例从最后一段开始计算,由终点E向前逐步倒推至起点A。
设Uk表示第K阶段的一个决策点,Fk(Uk)表示从第K阶段中的点Uk到达终点的最短路线的长度,Sk(Xk,Uk)表示第K阶段中Xk到Uk的距离。
(当K=5时,F5(E)=0)
当K=4时,F4(D1)=5,F4(D2)=2。
当K=3时,
F3(C1)=min[S3(C1,D1)+F4(D1),S3(C1,D2)+F4(D2)]=min[8,11]=8。
F3(C2)=min[S3(C2,D1)+F4(D1),S3(C2,D2)+F4(D2)]=min[7,11]=7。
F3(C3)=min[S3(C3,D2)+F4(D2)]=min[12]=12。
当K=2时,
F2(B1)=min[S2(B1,C1)+F3(C1),S2(B1,C2)+F3(C2)]=min[20,21]=20。
F2(B2)=min[S2(B2,C1)+F3(C1),S2(B2,C2)+F3(C2),S2(B2,C3)+F3(C3)]
=min[14,17,16]=14。
F2(B3)=min[S2(B3,C1)+F3(C1),S2(B3,C2)+F3(C2),S2(B3,C3)+F3(C3)]
=min[21,19,23]=19。
当K=1时,
F1(A)=min[S1(A,B1)+F2(B1),S1(A,B2)+F2(B2),S1(A,B3)+F2(B3)]
=min[22,19,20]=19。
其中X1(A)=B2,X2(B2)=C1,X3(C1)=D1,X4(D1)=E 组成一个最优策略。路线 A->B2->C1->D1->E 为从A到E的最短路线。最短路线长19。
从上面的计算过程中,我们可以看出,在求解的各个阶段,我们利用了K阶段与K+1阶段之间的如下关系:
Fk( Uk)=min[Sk(Uk,Xk)+Fk+1(Xk)] k=4,3,2,1
F5( U5)=0
这种递推关系,叫做动态规划的函数基本方程。
动态规划的最优化原理是“作为整个过程的最优策略具有这样的性质:无论过去的状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。”
与穷举法相比,动态规划的方法有两个明显的优点:
(1)大大减少了计算量
(2)丰富了计算结果
从上例的求解结果中,我们不仅得到由A点出发到终点E的最短路线及最短距离,而且还得到了从所有各中间点到终点的最短路线及最短距离,这对许多实际问题来讲是很有用的。
动态规划的最优化概念是在一定条件下,我到一种途径,在对各阶段的效益经过按问题具体性质所确定的运算以后,使得全过程的总效益达到最优。
动态规划问题的程序框图
下面是动态规划法[例]题解的程序核心框图
1.最短路径问题
(1)问题描述
设有如图所示有向图,边上的数字代表结点之间的距离,S1,S2,S3,S4,S5为起点,T1,T2,T3,T4,T5代表终点,试找出一条从起点到终点的最短路径。
如图中所指出的S4->A3->B3->C2->T3最短路径的长度为26。
(2)算法分析
当图中结点比较多,同时深度较深时,采用遍历算法时间上是不可取的。所以动态规划的方法适合本题。
·有向图的数据结构可用数组d表示:
实数类型三维数组d(N,M,3)
d(I,J,1)表示点(I,J)--(I-1,J+l)距离
d(I,J,2)表示点(I,J)--(I,J+l)距离
d(I,J,3)表示点(I,J)--(I+1,J+l)距离
即d数组的第一维表示行,d数组的第二维表示列,d数组的第三维表示点到另三个方向(右上、右边、右下)上的点的距离。
此时 d(1,1,1)=max,d(1,1,2)=3,d(1,1,3)=max { max 表示无通路 }
d(2,1,1)=7,d(2,1,2)=6,d(2,1,3)=max
d(5,1,1)=4,d(5,1,2)=max,d(5,1,3)=max
d(1,2,1)=max,d(1,2,2)=6,d(1,2,3)=7
d(2,2,1)=max ,d(2,2,2)=8,d(2,2,3)=12
max表示无通路
·算法的过程:从倒数第二列开始(C1,C2,C3,C4),当路径到达C1时,以下的最短为min(14,15)=10;同时记录下走的方向,为此,我们定义数组e:
三维实型数组e(N,M,2)
其中 e(I,J,1)表示从此点到达下一个点的最短路径;e(I,J,2)表示从此点到达下一个点的最短路径的方向:
1表示(I-1,J+1),2表示(I,J+1),3表示(I+1,J+1)
由此可知,最后一层的算法为:
FOR I=1 to N
从d(I,4,1),d(I,4,2),d(I,4,3)中找出最小值;
e(I,4,1)=最小值,e(I,4,2)=方向
NEXT I
在计算倒数第三列(B1,B2,B3,B4,B5)时:
如B3点计算为 mIn[(B3,C2)点距离+C2点最小值,(B2,C3)距离+C3点最小值]
由此可以得到一个统一的算法:
FOR I=M-1 TO 1 STEP -1
FOR J=1 TO N
求出d(I,J,1)+e(I-1,J+1,1)
d(I,J,2)+e(I,J+1,1)
d(I,J,3)+e(I+1,J+1,1)
NEXT J
NEXT I
其中的最小值填入e(I,J,1),e(I,J,2)方向为0,1或2。
在e(I,J)求出之后,再在e(1,1,2),……,e(2,1,1),e(I,1,1)中找出最小值,记为e(I,1,1),即第一列上的点的最短路径,然后求出达到最短路径的通路:
当d(I,1,2)=1时:I-1,2
当d(I,1,2)=2时:I,2
当d(I,1,2)=3时:I+1,2
以此方法,求出通路。
(3)程序清单
PROGRAM MIN_TRAIL(INPUT,OUTPUT);
CONST maxn = 20; maxm = 20; fname = 'q11.txt';
TYPE TMAP=ARRAY[0..MAXN,0..MAXM,1..3] OF REAL;
TYPE TMAKE=ARRAY[0..MAXN,0..MAXM,1..2] OF REAL;
VAR
n, m :BYTE;
D:TMAP;
E:TMAKE;
PROCEDURE INIT;
VAR
i, j, k :BYTE;
F:TEXT;
BEGIN
FILLCHAR(D,SIZEOF(D),0);
ASSIGN(F,FNAME);
RESET(F);
READLN(F,N,M);
FOR i := 1 TO n DO
FOR j := 1 TO m DO
FOR k := 1 TO 3 DO
BEGIN
READ (F, d[i, j, k]);
IF d[i, j, k] = 0 THEN
d[i, j, k] := 1E+08;
END;
CLOSE(F);
END;
FUNCTION getn(mm:BYTE):BYTE;
BEGIN
getn := n - ORD(NOT ODD(MM));
END;
PROCEDURE make;
VAR
i, j, k, p : INTEGER;
o, no : REAL;
BEGIN
FOR j := m - 1 DOWNTO 1 DO
FOR i := 1 TO getn(j) DO
BEGIN
o := 1E10;
p := 0;
FOR k := -1 TO 1 DO
BEGIN
no := e[i + k, j + 1, 1] + d[i, j, k + 2] ;
IF no < o THEN
BEGIN
p := k; o := no;
END;
END;
e[i, j, 1] := o;
e[i, j, 2] := p;
END;
END;
PROCEDURE OUTput;
VAR
i, j, p : INTEGER;
BEGIN
p := 1 ;
FOR i := 2 TO n DO
IF e[i, 1, 1] < e[p, 1, 1] THEN p := i;
WRITELN( 'Min Length=', e[p, 1, 1]:10:4);
WRITE('Trail is ', 'S', p);
FOR i := 1 TO m - 1 DO
BEGIN
WRITE('->');
IF i < m - 1 THEN WRITE( chr(64 + i) ) ELSE WRITE('T');
p := p + TRUNC(e[p, i, 2]);
WRITE(p);
END;
WRITELN;
END;
BEGIN
INIT;
MAKE;
OUTPUT;
END.
INPUT:
5 5
0 3 0 0 6 7 0 8 0 0 14 10 0 0 0
7 6 0 0 8 12 6 12 0 0 15 10 0 0 0
5 6 0 0 4 16 6 14 0 0 12 6 0 0 0
6 2 0 0 8 7 8 13 0 0 8 6 0 0 0
4 0 0 0 0 0 12 0 0 0 0 0 0 0 0
OUTPUT:
Min Length = 7.0000
Trail is S4 -> A4 -> B4 -> C3 -> T4
2.求最长不下降序列
(1)问题描述
设有由n个不相同的整数组成的数列,记为:
a(1)、a(2)、……、a(n)且a(i)<>a(j) (i<>j)
例如3,18,7,14,10,12,23,41,16,24。
若存在i1<i2<i3< … < ie 且有a(i1)<a(i2)< … <a(ie)则称为长度为e的不下降序列。如上例中3,18,23,24就是一个长度为4的不下降序列,同时也有3,7,10,12,16,24长度为6的不下降序列。程序要求,当原数列给出之后,求出最长的不下降序列。
(2)算法分析
根据动态规划的原理,由后往前进行搜索。
1·对a(n)来说,由于它是最后一个数,所以当从a(n)开始查找时,只存在长度为1的不下降序列;
2·若从a(n-1)开始查找,则存在下面的两种可能性:
①若a(n-1)<a(n)则存在长度为2的不下降序列a(n-1),a(n)。
②若a(n-1)>a(n)则存在长度为1的不下降序列a(n-1)或a(n)。
3·一般若从a(i)开始,此时最长不下降序列应该按下列方法求出:
在a(i+1),a(i+2),…,a(n)中,找出一个比a(i)大的且最长的不下降序列,作为它的后继。
4·为算法上的需要,定义一个数组
整数类型二维数组d(N,3)
d(I,1)表示点a(i)
d(I,2)表示从I位置到达N的最长不下降序列长度
d(I,3)表示从I位置开始最长不下降序列的下一个位置
初始化:
FOR I = 1 TO N
INPUT #1, D(I, 1)
D(I, 2) = 1
D(I, 3) = 0
NEXT I
下面给出求最长不下降序列的算法:
FOR I = N - 1 TO 1 STEP
-1
L = 0: P = 0
FOR J = I + 1 TO N
IF D(I, 1) < D(J, 1) AND D(J, 2) > L THEN
L = D(J, 2)
P = J
END IF
NEXT J
IF L > 0 THEN
D(I, 2) = L + 1
D(I, 3) = P
END IF
NEXT I
下面找出最长不下降序列:
DMAX = D(1, 2)
L = 1
FOR I = 2 TO N - 1
IF D(I, 2) > DMAX THEN
DMAX = D(I, 2)
L = I
END IF
NEXT I
最长不下降序列长度为D(DMAX, 2)
序列
WHILE L <> 0
PRINT D(L, 1);
L = D(L, 3)
WEND
(3)程序清单
PROGRAM MAX_RISE(INPUT,OUTPUT);
CONST MAXN=100;FNAME='Q21.TXT';
TYPE TLIST=ARRAY[1..MAXN,1..3] OF INTEGER;
VAR D:TLIST;N:BYTE;
PROCEDURE INIT;
VAR
I:INTEGER;
F:TEXT;
BEGIN
ASSIGN(F,FNAME);
RESET(F);
READLN(F,N);
FOR I:=1 TO N DO
BEGIN
READ(F,D[I,1]);
D[I,2]:=1;
D[I,3]:=0
END;
CLOSE(F);
END;
PROCEDURE MAKE;
VAR
I,J,P:BYTE;
L:INTEGER;
BEGIN
FOR I:=N-1 DOWNTO 1 DO
BEGIN
L:=0;P:=0;
FOR J:=I+1 TO N DO
IF (D[I,1]<D[J,1]) AND (D[J,2]>L) THEN
BEGIN
L:=D[J,2];
P:=J;
END;
IF L>0 THEN
BEGIN
D[I,2]:=L+1;
D[I,3]:=P;
END;
END;
END;
PROCEDURE OUTPUT;
VAR
I,L,DMAX:BYTE;
BEGIN
WRITE('SOURCE:');
FOR I:=1 TO N DO WRITE(D[I,1]:5);
WRITELN;
DMAX:=D[1,2];
L:=1;
FOR I:=2 TO N-1 DO
IF D[I,2]>DMAX THEN
BEGIN
DMAX:=D[I,2];
L:=I;
END;
WRITE('RESULT IS: ');
WHILE L<>0 DO
BEGIN
WRITE(D[L,1]:5);
L:=D[L,3];
END;
WRITELN;
WRITELN('MAX LENGTH=',D[DMAX,2]);
END;
BEGIN
INIT;
MAKE;
OUTPUT;
END.
INPUT:
10
3 18 7 14 10 12 23 41 16 24
OUTPUT:
SOURCE: 3 18 7 14 10 12 23 41 16 24
RESULT IS: 3 7 10 12 23 41
MAX LENGTH = 6
3.最小代价子母树
(1)问题描述
最优化原理应在子问题求解中体现。有些问题也允许顺推。
给定一个正整数序列a(1),a(2),...,a(n)不改变序列中每个元素在序列中的位置,把它们相加,井用括号记每次加法所得的和,称为中间和。
编程完成任务:
1·添上n-l对括号,加法运算依括号顺序进行,得到n-l个中间和;
2·求出使中间和最少的添括号方法。
例如给出序列是4,1,2,3。
第一种添括号方法:
((4+1)+(2+3))=((5)+(5))=(10),
有三个中间和是5,5,10,它们之和为:5+5+10=20,
第二种添括号方法:(4+((1+2)+3))=(4+((3)+3))=(4+(6))=(10),
中间和是3,6,10,它们之和为19。
(2)算法分折
1·把序列的n个数,看成二元树的叶子(二叉树)的权。如果用一种括号把两个正整数括起来,则它们对应结点的父辈的权就是由这次加法产生的中间和。这样就把对序列任何一种加括号的方法与一棵带权的二元树对应。上面例子中加括号方法对应二元树如图
一棵带权二元树的代价就是树中所有根结点权之和。代价最小的带权二元树称为最优二元树。问题转化为求最优带权二元树。
那么,什么是最优带权二元树呢?
最优二叉树,又称哈夫曼树,是一类带权路径长度最短的树,有着广泛的应用。
我们首先给出路径和路径长度的概念。从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径,路径上的分支数目称做路径长度。树的路径长度是从树根到每一结点的路径长度之和。这种路径长度最短的二叉树是。
若将上述概念推广到一般情况,考虑带权的结点。结点的带权路径长度为从该结点树根之间的路径长度与结点上权的乘积。树的带权路径长度为树中所有叶子结点的带路径长度之和,通常记作
WPL=∑W(k)L(k) k=1...n
假设有n个权值W(1),W(2),......,W(n),试构造一棵有n个叶子结点的二叉树,每个叶子结点带权为W(k),则其中带权路径长度WPL最小的二叉树称做最优二又树或哈夫显树。
例如,图中的两棵二叉树,都有4个叶子结点a、b、c、d,分别带权4,1,2,3,它们的带权路径长度分别为
(a)WPL=4×2十1×2十2×2十3×2=20
(b)WPL=4×1十1×3十2×3十3×2=19
如何构造哈夫曼树呢?俗称哈夫曼算法。现叙述如下:
(1)根据给定的n个权值W(1),W(2),......,W(n)构成n棵二叉树的集合F={T(1),T(2),......,T(n)}
其中每棵二叉树T(k)中只有一个带权为W(k)的根结点,其左右子树均空。
(2)在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左、右子树上根结点的权值之和。
(3)在F中删除这两棵树,同时将新得到的二叉树加入F中。
(4)重复(2)和(3),直到F只含一棵树为止。这棵树便是哈夫曼树。
2·如果一棵带权的二元树是最优的,那末它的任何一棵子树对于这棵子树的树叶来说也是最优的。因此,带权最优二元树的问题符合最优化原则。可以用动态归划方法求解。
3·对于这个问题在决策时要考虑到树叶序列的划分方案。
[状态分析]
1·从序列4,4,8,5,4,3,5分析它的二元权的结构。图给出两棵二元树(a)、(b)。
这棵7片叶叶的二元树,图(a)中的树的左子树是三片叶子,右子树是4片叶子,对应于树叶的一种划分方案。记为(左3,右4)。对应添号形式是:(4+4)+8)+((5+4)+(3+5)))其代价和为91。
图(b)对应于另一种划分方案记为(左4,右3),其代价和是94。可见,由于树叶的划分方案不同,所得的二元树的代价也不同。对七片叶子的二元树,可以有
(左1,右6),(左2,右5),(左3,右4),(左4,右3),(左5,右2),(左6,右1)六种划分方法,对于每一种划分方法都对元一棵二元树,从中找出最优的二元树。
2·决策的前提是最优化原理。对于每一种划分方案,比如(左3,右4),应该知道前三数4,4,8作为树叶构成最优的子二元树是什么,也应知道5,4,3,5作为树叶构成最优二元树的最优子二元树是什么。根据最优化原理,由这棵最优子二元树合成的二元树一定是对于(左3,右4)划分方案最优二元树。从此可见,二元树的构成中每一步的决策不仅与前一步决定有关,而且与若干步的决策有关。二元树的代价计算是一个递推过程。我们选用序列4,4,8,5,4,3,5把它可能构成的各类带权二元树的代价与权的递推计算方法表示如图中。
计算方法说明如下,
[1]一片树叶权重为叶片的权,代价为0,得图的第一层。
[2]二片树叶。它的构成状态共有:(4,4),(4,8),(8,5),(5,4),(4,3),(3,5)共六种。括号内数字之和是中间和即为子树根的权。于是生成2片树叶权重。因为1片树叶权为0,故2片的代价与权重一样。
[3]三片树叶,它的构成状态有:(4,4,8),(4,8,5),(8,5,4),(5,4,3),(4,3,5)共计5种。于是生成二片树叶的权。代价计算时应考虑3片树叶的划分状态可能(左1,右2)或(左2,右1),3片树叶的代价应考虑它们代价中的最小者。若是对(4,4,8)进行划分时其状态有:
(1)以(左1,右2)划分时其状态有(4),(4,8),其中(4)的代价为0,(4,8)代价为12。代价和为12=0+12。
(2)以(左2,右1)划分时其状态有(4,4),(8),其中(4,4)的代价为8,8的代价为0。代价和为8。
因为8比12小,3片树叶4,4,8的代价是它的权加上划分状态中代价小的。于是得出第一个代价为16+8=24。余类推。
[4]四片树叶时,不难算出其权重为21,21,20,17。代价计算应考虑前面划分的可能情况。以树叶权重为4,4,8,5为例。它的划分方法有:
(左1,右3),(左2,右2),(左3,右1)三种。
把每一种划分的代价计算如下:
(1)以(左1,右3)划分状态是:(4),(4,4,5),其中(4)的代价为0,(4,4,5)代价为29,0+29=29(代价和)。
(2)以(左2,右2)划分状态是:(4,4),(8,5),其中(4,4)的代价为8,(8,5)代价为13,8+13二21(代价和)。
(3)以(左3,右1)划分状态是:(4,4,8),(5),其中(4,4,8)的代价24,(5)的代价为0,24十0二24(代价和)。
这三种划分状态代价和最小的是21。于是4片中4,4,8,5的权为21。代价为21+21=42。其他代价可仿此计算。
[5]五片树叶时,权重不难算出。代价计算应考虑划分:
(左1,右4),(左2,右3),(左3,右2),(左4,右1)的代价。
当权重为25,树叶是4,4,8,5,4。
划分(左1,右4)的状态是(4),(4,8,5,4),代价和为0+42=42。
划分(左2,右3)的状态是(4,4),(8,5,4),代价和为8+26=34。
因为8,5,4是三片叶子中第三个,从3片树叶的代价中查到26。
划分(左3,右2)的状态是(4,4,8),(5,4)。由图中查得代价和为24+9=33。
划分(左4,右1)的状态是(4,4,8,5),(4),由图中查得代价和为42+0=42。
于是关于权重为25,树叶为4,4,8,5,4的代价为25+33=58。
当权重为24,树叶为4,8,5,4,3。
划分(左1,右4)代价为0+39=39
划分(左2,右3)代价为12+19=31
划分(左3,右2)代价为29+7=36
划分(左4,右1)代价为42+0=42
31是最小代价,权重为24,于是得代价为31+24=55。
当权重为25,树叶为8,5,4,3,5按划分计算可得其代价为57。
其他的计算由读者去完成。
从以上分析,带权二元树的代价计算是一个比较复杂的递推过程。高层的代价计算,必须用本身的划分状态,利用底层的代价进行计算。虽然递推关系比较复杂,对大多数问题来说,动态规划算法的复杂性有可能是多项式级的。动态规划是一个很有实用价值的用途甚广的组合算法。
(3)程序清单
PROGRAM MIN_COST(INPUT,OUTPUT);
CONST MAXN=100;FNAME='Q31.TXT';
TYPE TLIST=ARRAY[1..MAXN,1..MAXN] OF INTEGER;
VAR B,SUM,DE:TLIST;
N:BYTE;
PROCEDURE INIT;
VAR
I,J:INTEGER;
F:TEXT;
BEGIN
FILLCHAR(B,SIZEOF(B),0);
FILLCHAR(SUM,SIZEOF(SUM),0);
FILLCHAR(DE,SIZEOF(DE),0);
ASSIGN(F,FNAME);
RESET(F);
READLN(F,N);
FOR I:=1 TO N DO
READ(F,SUM[1,I]);
CLOSE(F);
FOR I:=2 TO N DO
FOR J:=1 TO N-I+1 DO
SUM[I,J]:=SUM[I-1,J]+SUM[1,I-1+J];
END;
PROCEDURE MAKE;
VAR
I,J,K,P:BYTE;
O,NO:INTEGER;
BEGIN
FOR I:=2 TO N DO
FOR J:=1 TO N-I+1 DO
BEGIN
P:=0;O:=MAXINT;
FOR K:=1 TO I-1 DO
BEGIN
NO:=SUM[I,J]+B[K,J]+B[I-K,J+K];
IF NO<O THEN
BEGIN
O:=NO;
P:=K;
END;
END;
B[I,J]:=O;
DE[I,J]:=P;
END;
END;
PROCEDURE PRINT;
VAR
I:INTEGER;
A:ARRAY[1..MAXN] OF INTEGER;
X1,X2:INTEGER;
PROCEDURE PA;
VAR I:INTEGER;
BEGIN
IF X1=0 THEN EXIT;
FOR I:=1 TO N DO
IF A[I]<>0 THEN
BEGIN
IF (I=X1) OR (I=X2) THEN WRITE('-');
WRITE(A[I]);
WRITE(' ');
END;
WRITELN;
END;
PROCEDURE SHOW(I,J:INTEGER);
VAR
I1,I2,J1,J2:BYTE;
BEGIN
I1:=DE[I,J];
J1:=J;
I2:=I-I1;
J2:=J+I1;
IF I>2 THEN
BEGIN
SHOW(I1,J1);
SHOW(I2,J2);
END;
X1:=J1;
X2:=J2;
IF X1<>X2 THEN
BEGIN
PA;
A[J1]:=A[J1]+A[J2];
A[J2]:=0;
END;
END; {OF SHOW}
BEGIN
FOR I:=1 TO N DO
A[I]:=SUM[1,I];
SHOW(N,1);
X1:=-10;
X2:=-10;
PA;
WRITELN('MIN COST=',B[N,1]);
END;
BEGIN
INIT;
MAKE;
PRINT;
END.
INPUT:
10
3 18 7 14 10 12 23 41 16 24
OUTPUT:
- 3 - 18 7 14 10 12 23 41 16 24
21 - 7 - 14 10 12 23 41 16 24
- 21 - 21 10 12 23 41 16 24
42 - 10 - 12
23 41 16 24
42 - 22 - 23 41 16 24
- 42 - 45 41 16 24
87 41 - 16 - 24
87 - 41 - 40
- 87 - 81
168
MIN COST = 527
4.背包问题
(1)问题描述
设有n种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为XK,今从n种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于XK,而价值的和为最大。
(2)算法分析
·记W(1),W(2),…,W(N)每种物品的重量,V(1),V(2),…,V(N)为每种物品的价值,X(1),X(2),…,X(N)为每种物品选取的数量,问题成为满足:
条件 ∑ X(I)W(I)≤XK 且 ∑ X(I)V(I) 为最大。
·记FK(Y)为取前K种物品,限制重量为Y时取得价值最大,则:
F0(Y)=0,即对一切Y,一件物品都不取时,最大价值为0;
FK(0)=0,即最大重量限制为0时,不能取得任何物品,所以最大价值也为0;
F1(Y)=[y/w1]u1,即仅取第一种物品,则最大价值为尽可能多的装第一种物品,所以能装的数量为[y/w1],而得到的价值为[y/w1]u1。
一般公式:FK(Y)=max{ FK-1(Y),FK(Y-WK)+VK}
并约定,当Y<0时,FK(Y)=负无穷大。
下面用一个具体的例子来说明求解的过程。
N=4,XK=10
W1=2,W2=3,W3=4,W4=7
V1=1,V2=3,V3=5,V4=9
下面列出FK(Y):
注1·第1行表示k=1,即取第一种物品,当Y=l时,无法取,所以F1(1)=0,当y=2时,可取1个第一种物品,价值为1,…,当y=10(即XK),此时,可取5个第一种物品,价值为5。
注2·当可以取2种物品时。
当y=1时,为0(什么都不能取);
当y=2时,仅能取第一种物品,所以F2(2)=1;
当y=3时,有2种取法。取一个第一种物品,价值为1;取一个第二种物品,价值为3,取大者,所以F2(3)=3。
当y=4也有2种取法,取第一种物品2件,价值为2;取第二种物品1件价值为3,所以F2(4)=3。
当y=5时,有2种取法,取第一种物品2件,价值为2;取第一种物品,第二种物品各1件,价值为4,所以F2(5)=4。以此类推。计算F2(10)时,有2种考虑,第一种是全部取第一种,即F1(10)=5;第二种是由F2(7)+3=6+9,取大者,得到F2(10)=9。
注3·
注4·F4(10)的计算方法为:
上面给出的是计算F(I,J)的方法,但是还不能确定每种物品的选取个数。下面我们用倒推法来求出每种物品的个数。
记F(N,XK)为目标,检查:F(N-1,XK)与F(N,XK-WN)+UN
若前者大,则无输出,由F(N,XK)-> F(N-1,XK)。
若后者大,则输出WN,计算F(N,XK-WN)。
当N-1,N-2,…,到达1时,则全部输出。
(3)程序清单
PROGRAM PACKAGE(INPUT,OUTPUT);
CONST MAXXK=400;MAXN=20;FNAME='Q41.TXT';
TYPE TLIST=ARRAY[1..MAXN] OF BYTE;
TMAKE=ARRAY[0..MAXN,0..MAXXK] OF INTEGER;
VAR N,XK:INTEGER;
W,U:TLIST;
F:TMAKE;
PROCEDURE INIT;
VAR
I:BYTE;
F:TEXT;
BEGIN
FILLCHAR(W,SIZEOF(W),0);
FILLCHAR(U,SIZEOF(U),0);
ASSIGN(F,FNAME);
RESET(F);
READLN(F,N,XK);
FOR I:=1 TO N DO
READ(F,W[I]);
FOR I:=1 TO N DO
READ(F,U[I]);
CLOSE(F);
END;
PROCEDURE MAKE;
VAR
I,J:BYTE;
BEGIN
FILLCHAR(F,SIZEOF(F),0);
FOR I:=1 TO N DO
BEGIN
FOR J:=1 TO W[I]-1 DO
F[I,J]:=F[I-1,J];
FOR J:=W[I] TO XK DO
IF F[I-1,J]>F[I,J-W[I]]+U[I] THEN
F[I,J]:=F[I-1,J]
ELSE
F[I,J]:=F[I,J-W[I]]+U[I];
END;
END;
PROCEDURE PRINT;
VAR
GET:TLIST;
I,J:BYTE;
BEGIN
FILLCHAR(GET,SIZEOF(GET),0);
I:=N;J:=XK;
WHILE I>0 DO
IF F[I,J]=F[I-1,J] THEN DEC(I)
ELSE
BEGIN
DEC(J,W[I]);
INC(GET[I]);
END;
WRITELN('N=',N,' ','XK=',XK);
WRITELN('MAX WORTH=',F[N,XK]);
FOR I:=1 TO N DO
WRITELN(' NO.',I,
' WEIGHT:',W[I]:2,
' WORTH:',U[I]:2,
' GET',GET[I]:2);
END;
BEGIN
INIT;
MAKE;
PRINT;
END.
INPUT:
4 10
2 3 4 7
1 3 5 9
OUTPUT:
N=: 4 XK=: 10
MAX WORTH = 12
NO: 1 WEIGHT: 2 WORTH: 1 GET: 0
NO: 2 WEIGHT: 3 WORTH: 3 GET: 1
NO: 3 WEIGHT: 4 WORTH: 5 GET: 0
NO: 4 WEIGHT: 7 WORTH: 9 GET: 1
5.四塔问题
(1)问题描述
三塔问题是大家非常熟悉的问题,下面详细说明四塔问题。设有A,B,C,D四个柱子(有时称塔),在A柱上有由小到大堆放的n个盘子,如图所示。
今将A柱上的盘子移动到D柱上去。可以利用B,C柱作为工作栈用,移动的规则如下:
①每次只能移动一个盘子。
②在移动的过程中,小盘子只能放到大盘子的上面。
③下面给出盘子的个数为n的一般方法,用F4(n,a,b,c,d)表示四塔问题,从A到D移动n个盘子,可以利用b,c作为工作单元,四塔问题可以分解为:
F4(n,a,b,c,d)=>F4(r,a,c,d,b)+F3(n-r,a,c,d)
F4(rc,a,b,d)
即如何选择r使F4(n,a,b,c,d)为最小。r的取值范围为1,2,…,n-1,为此,我们构造2个整型数组:
F3(N),F4(N)
其中F3用来存放三塔问题的解。
(3)程序清单
PROGRAM SOLVE_4_HOINA(INPUT,OUTPUT);
const MAXN=1000;
TYPE
TR=ARRAY[1..MAXN] OF INTEGER;
TFUNC=ARRAY[1..MAXN] OF double;
VAR
N:INTEGER;
F3,F4:tfunc;
PROCEDURE INIT;
VAR
I:INTEGER;
BEGIN
FILLCHAR(F3,SIZEOF(F3),0);
FILLCHAR(F4,SIZEOF(F4),0);
WRITE(' INPUT N: ');
READLN(N);
F3[1]:=1;
F4[1]:=1;
FOR I:=2 TO N DO
F3[I]:=(F3[I-1]+1)*2-1;
END;
PROCEDURE MAKE;
VAR
I,J:INTEGER;
K:DOUBLE;
BEGIN
FOR I:=2 TO N DO
BEGIN
K:=1E+38;
FOR J:=1 TO I-1 DO
IF 2*F4[J]+F3[I-J]<K THEN
K:=2*F4[J]+F3[I-J];
F4[i]:=K;
END;
END;
BEGIN
INIT;
MAKE;
WRITELN(' TOTAL=',F4[N]);
END.
(*INPUT N:10
TOTAL=49
INPUT N:1000
TOTAL=932385860354049*)
INPUT: 10
TOTAL = 49
INPUT N: 1000
TOTAL = 932385860354049
6.最小代价
(1)问题描述
设有一个NxM的方格如图所示,在方格中去掉某些点,方格边上的数字代表距离,试找出一条从A到B的路径,经过的距离和为最小(此时称为最小代价),从A前进的方向只能向右,或向下,而被去掉点的代价可看成无穷大。
(2)算法分析
采用动态规划的方法,从B向A方向推导:
当路径到达B1之后,下面仅有一条通路B1->B。而最小距离为B1B。同样,当路径到达B5之后,下面仅有一条通路B5->B,而最小距离为B5B,但当路径达到B3时,有2条通路,B3B1,B1B,或B3B5,B5B,而最小路径为min{B3B+B1最小路径,B3B5+B5最小路径}。通常在方格中某个坐标点(i,i),其最小路径为:
min{(I,J)-(I,J+1)+(I,J+1)最小路径,(I,J)->(I+1,J)+(I+1,J)的最小路径}。为了记录求出的路径,用数组D(N,M,2)来记录,其中:
D(I,J,1)表示最小路径,D(I,J,2)表示下达的方向,1----向右,2----向下。
而约定D(N,M,1)=0,D(N,M,2)=0。
(3)程序清单
PROGRAM MIN_COST(INPUT,OUTPUT);
CONST maxMN = 30;Fname = 'Q61.txt';
TYPE
TMAP=ARRAY[1..MAXMN,1..MAXMN] OF INTEGER;
TMAKE=ARRAY[1..MAXMN,1..MAXMN,1..2] OF WORD;
VAR
N,M:BYTE;
A:TMAP;
D:TMAKE;
PROCEDURE INIT;
VAR
i, j :BYTE;
F:TEXT;
BEGIN
FILLCHAR(A,SIZEOF(A),0);
FILLCHAR(D,SIZEOF(D),0);
ASSIGN(F,FNAME);
RESET(F);
READLN(F,N,M);
FOR i := 1 TO n DO
FOR J:=1 TO M DO
BEGIN
READ(F,A[I,J]);
IF A[I,J]=0 THEN
A[I,J]:=MAXINT;
END;
CLOSE(F);
END;
PROCEDURE MAKE;
VAR
i, j : BYTE;
BEGIN
FILLCHAR(D[N,M],SIZEOF(D[N,M]),0);
FOR I:=N-1 DOWNTO 1 DO
BEGIN
D[I,M,1]:=D[I+1,M,1]+A[I,M];
D[I,M,2]:=2;
END;
FOR J:=M-1 DOWNTO 1 DO
BEGIN
D[N,J,1]:=D[N,J+1,1]+A[N,J];
D[N,J,2]:=1;
END;
FOR I:=N-1 DOWNTO 1 DO
FOR J:=M-1 DOWNTO 1 DO
IF D[I+1,J,1]<D[I,J+1,1]
THEN
BEGIN
D[I,J,1]:=D[I+1,J,1]+A[I,J];
D[I,J,2]:=2;
END
ELSE
BEGIN
D[I,J,1]:=D[I,J+1,1]+A[I,J];
D[I,J,2]:=1;
END;
END;
PROCEDURE PRINT;
VAR
I,J:BYTE;
BEGIN
WRITELN(' GO THIS WAY : ');
WRITE(' ');
I:=1;J:=1;
WHILE (I<N) OR (J<M) DO
BEGIN
WRITE('(',I:2,',', J:2,')-->');
IF D[I,J,2]=1
THEN INC(J)
ELSE INC(I);
END;
WRITELN('(',N:2,',', M:2,')');
WRITELN(' MIN COST = ',D[1,1,1]);
END;
BEGIN
INIT;
MAKE;
PRINT;
END.
INPUT:
4 4
10 3 4 7
1 11 0 2
5 5 8 4
0 6 6 5
OUTPUT:
GO THIS WAY:
(1,1)-->(1,2)-->(1,3)-->(1,4)-->(2,4)-->(3,4)-->(4,4)
MIN COST = 30
7.挖地雷
(1)问题描述
在一条公路上埋有若干堆地雷,每堆地雷有一定的数量,地雷堆的编号为1,2,…,N,例如,埋有地雷数量如下:8 14 2 17 33 26 15 17 19 6
此时,地雷的数量可用一维数组A(N)表示。同时,给出地雷堆之间的联系,从第1堆开始,它指出挖了此堆之后,还可以选择继续往下挖,若存在多种方案,只能选择其中的一种,若没有任何后继的方案,则挖地雷结束。例如,可给出下面的关系:
从上图可看出,若从第1堆开始挖,首先得到8枚地雷,然后,下面有2种选择3、4,若选择3,则可挖到2枚,下面还可以继续挖,或选择4此时可挖到17枚,到此结束,总共可挖到8+17=25枚。或选择1--3--6--7--8--10此时可挖到74枚。但也可以从2开始挖,后选择2--3--6--7--8--10,则共可挖到14+26+15+17+6=80枚,但还不是最多的,最多的为5--6--7--8--10,此时共可挖到33+26+15+17+6=97枚。地雷堆之间的联系可用以下的数组表示:
二维整数型数组R(I,J)表示,当R(I,J)=1表示从I到J有通路,当R(I,J)=0表示无通路。
上例中的 R如下图
(2)算法分析
用动态规划求解:
1·由后往前查找:
若从第n堆地雷开始挖,此时可能得到a(n)枚地雷。
若从第n-1堆地雷开始挖,此时有2种可能:
①n-1堆到n堆无联系(可由R(n-1,n)判断),此时可挖a(n-1)枚。
②n-1堆到n堆有联系,则可挖a(n-1)+a(n)枚。
一般情况,若从第i堆开始挖,根据关系R,找出i后面的所有联系,从中找出一个可挖最多地雷的作为联系,这样可得到最多的挖地雷数。
2·上面计算出从每个堆开始能挖到最多的地雷数,此时找出一个最大值即可。
3·算法流程。
①定义数据结构,整数型二维数组B(N,2)
B(i,1)表示最多可挖地雷数。
B(i,2) 向后连接。
②求从第i堆地雷开始挖的数量的算法:
CMAX=0:H=0
FOR J=I+1 TO N
IF R(I,J)=1 AND B(J,1)>CMAX THEN
H=J:CMAX=B(J,1)
END IF
NEXT J
B(I,1)=A(I)+CMAX
B(I,2)=H
③找出最大值并找出挖的路径:
CMAX=0:H=0
FOR I=1 TO N
IF B(I,1)>CMAX THEN
H=I
CMAX=B(I,1)
END IF
NEXT I
PRINT "最大可挖数量:",CMAX
PRINT H
WHILE H<>0
H=B(H,2)
PRINT "->",H;
WEND
(3)程序清单
PROGRAM GROUND_BOMB(INPUT,OUTPUT);
CONST max = 100;Fname = 'Q71.txt';
TYPE
TLEI=ARRAY[1..MAX] OF INTEGER;
TNET=ARRAY[1..MAX,1..MAX] OF 0..1;
TWAY=ARRAY[0..MAX,1..2] OF INTEGER;
VAR
A :TLEI;
R:TNET;
B:TWAY;
N:BYTE;
PROCEDURE INIT;
VAR
i, j :BYTE;
F:TEXT;
BEGIN
FILLCHAR(A,SIZEOF(A),0);
FILLCHAR(R,SIZEOF(R),0);
FILLCHAR(B,SIZEOF(B),0);
ASSIGN(F,FNAME);
RESET(F);
READLN(F,N);
FOR i := 1 TO n DO
READ(F,A[I]);
FOR I := 1 TO N DO
FOR J := I TO N DO
READ (F, R[i, j]);
CLOSE(F);
END;
PROCEDURE WORK;
VAR
i, j, H : BYTE;
CMAX : INTEGER;
BEGIN
FOR I := N DOWNTO 1 DO
BEGIN
CMAX := 0;H := 0;
FOR J :=I+1 TO N DO
IF (R[I,J]=1) AND (B[J,1]>CMAX) THEN
BEGIN
H:=J;
CMAX:=B[j,1];
END;
B[I,1]:=A[I]+CMAX;
B[I,2]:=H;
END;
CMAX:=0;
H:=0;
FOR I:=1 TO N DO
IF B[I,1]>CMAX THEN
BEGIN
CMAX:=B[i,1];
H:=I;
END;
WRITELN(' MAX GET: ',CMAX);
IF H<>0 THEN WRITE(H);
WHILE B[H,2]<>0 DO
BEGIN
H:=B[H,2];
WRITE(' --> ',H);
END;
WRITELN;
END;
BEGIN
INIT;
WORK;
END.
INPUT:
6
8 14 2 17 9 26
0 0 1 1 0 0
0 1 0 0 0
0 1 0 1
0 0 0
0 1
0
OUTPUT:
MAX GET : 42
2 ----> 3 ----> 6
PROGRAM SOLVE_4_HOINA(INPUT,OUTPUT);
const MAXN=1000;
TYPE
TR=ARRAY[1..MAXN] OF INTEGER;
TFUNC=ARRAY[1..MAXN] OF double;
VAR
N:INTEGER;
F3,F4:tfunc;
PROCEDURE INIT;
VAR
I:INTEGER;
BEGIN
FILLCHAR(F3,SIZEOF(F3),0);
FILLCHAR(F4,SIZEOF(F4),0);
WRITE(' INPUT N: ');
READLN(N);
F3[1]:=1;
F4[1]:=1;
FOR I:=2 TO N DO
F3[I]:=(F3[I-1]+1)*2-1;
END;
PROCEDURE MAKE;
VAR
I,J:INTEGER;
K:DOUBLE;
BEGIN
FOR I:=2 TO N DO
BEGIN
K:=1E+38;
FOR J:=1 TO I-1 DO
IF 2*F4[J]+F3[I-J]<K THEN
K:=2*F4[J]+F3[I-J];
F4[i]:=K;
END;
END;
BEGIN
INIT;
MAKE;
WRITELN(' TOTAL=',F4[N]);
END.
(*INPUT N:10
TOTAL=49
INPUT N:1000
TOTAL=932385860354049*)
INPUT: 10
TOTAL = 49
INPUT N: 1000
TOTAL = 932385860354049
动态规划问题的经典实例逆推
(1)问题的提出
首先,例举一个典型的且很直观的多阶段决策问题:
[例] 下图表示城市之间的交通路网,线段上的数字表示费用,单向通行由A->E。试用动态规划的最优化原理求出A->E的最省费用。
如图从A到E共分为4个阶段,即第一阶段从A到B,第二阶段从B到C,第三阶段从C到D,第四阶段从D到E。除起点A和终点E外,其它各点既是上一阶段的终点又是下一阶段的起点。例如从A到B的第一阶段中,A为起点,终点有B1,B2,B3三个,因而这时走的路线有三个选择,一是走到B1,一是走到B2,一是走到B3。若选择B2的决策,B2就是第一阶段在我们决策之下的结果,它既是第一阶段路线的终点,又是第二阶段路线的始点。在第二阶段,再从B2点出发,对于B2点就有一个可供选择的终点集合(C1,C2,C3);若选择由B2走至C2为第二阶段的决策,则C2就是第二阶段的终点,同时又是第三阶段的始点。同理递推下去,可看到各个阶段的决策不同,线路就不同。很明显,当某阶段的起点给定时,它直接影响着后面各阶段的行进路线和整个路线的长短,而后面各阶段的路线的发展不受这点以前各阶段的影响。故此问题的要求是:在各个阶段选取一个恰
当的决策,使由这些决策组成的一个决策序列所决定的一条路线,其总路程最短。如何解决这个问题呢?
(2)算法分折
用动态规划法逆推求解
决策过程:逆推
[1]由目标状态E向前推,可以分成四个阶段,即四个子问题。如上图所示。
[2]策略:每个阶段到E的最省费用为本阶段的决策路径。
[3]D1,D2是第一次输人的结点。他们到E都只有一种费用,在D1框上面标5,D2框上面标2。目前无法定下,那一个点将在全程最优策略的路径上。第二阶段计算中,5,2都应分别参加计算。
[4]C1,C2,C3是第二次输入结点,他们到D1,D2各有两种费用。此时应计算C1,C2,C3分别到E的最少费用。
C1的决策路径是 min{(C1D1),(C1D2)}。计算结果是C1+D1+E,在C1框上面标为8。
同理C2的决策路径计算结果是C2+D2+E,在C2框上面标为7。
同理C3的决策路径计算结果是C3+D2+E,在C3框上面标为12。
此时也无法定下第一,二阶段的城市那二个将在整体的最优决策路径上。
[5]第三次输入结点为B1,B2,B3,而决策输出结点可能为C1,C2,C3。仿前计算可得Bl,B2,B3的决策路径为如下情况。
Bl:B
1C
1费用 12+8=20, 路径:B1+C1+D1+E
B2:B
2C
1费用 6+8=14, 路径:B2+C1+D1+E
B3:B
2C
2费用 12+7=19, 路径:B3+C2+D2+E
此时也无法定下第一,二,三阶段的城市那三个将在整体的最优决策路径上。
[6]第四次输入结点为A,决策输出结点可能为B1,B2,B3。同理可得决策路径为
A:AB2,费用5+14=19,路径 A+B2+C1+D1+E。
此时才正式确定每个子问题的结点中,那一个结点将在最优费用的路径上。19将结果显然这种计算方法,符合最优原理。子问题的决策中,只对同一城市(结点)比较优劣。而同一阶段的城市(结点)的优劣要由下一个阶段去决定。
[7]小结及比较
动态规划的最优化原理是“作为整个过程的最优策略具有这样的性质:无论过去的状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。”
与穷举法相比,动态规划的方法有两个明显的优点:
(1)大大减少了计算量
(2)丰富了计算结果
从上例的求解结果中,我们不仅得到由A点出发到终点E的最短路线及最短距离,而且还得到了从所有各中间点到终点的最短路线及最短距离,这对许多实际问题来讲是很有用的。
动态规划的最优化概念是在一定条件下,我到一种途径,在对各阶段的效益经过按问题具体性质所确定的运算以后,使得全过程的总效益达到最优。
[8]应用动态规划要注意
1.阶段的划分是关键,必须依据题意分析,寻求合理的划分阶段(子问题)方法。而每个子问题是一个比原问题简单得多的优化问题。而且每个子问题的求解中,均利用它的一个后部子问题的最优化结果,直到最后一个子问题所得最优解,它就是原问题的最优解。
2.变量太多,同样会使问题无法求解。
3.最优化原理应在子问题求解中体现。有些问题也允许顺推。
(3)程序清单
Program Short(input,output);
Const Max=100;
Var
N,St,En, { 顶点数,起点,终点 }
I,J,X : Integer;
way : Array[1..Max,1..Max] of Integer; { 线路网络的带权矩阵 }
NetDot : Array[1..Max] of Record
{NetDot[j].Ne--从J点出发的决策}
{NetDot[j].CostSt--从J点至En点的最短距离}
Ne,Cost:Integer
End;
F : Text; { 文件变量 }
Str : String; { 文件名串 }
Begin
Write('File name=',Str); { 读入文件名串 }
Readln(Str);
Assign(F,Str); { 文件名与文件变量连接 }
Reset(F); { 读文件准备 }
Readln(F,N,St,En); { 读入顶点数,起点,终点 }
(*writeln('N=',N:4,'St=',St:4,'En=',En:4);*)
For I:= 1 to N Do { 读入各点间连线的距离 }
For J:= 1 to N Do Read(F,Way[I,J]);
Close(F);
For I:= 1 to N Do NetDot[I].Cost:=Maxint; { St至各点的最短路线长度初始化 }
NetDot[En].Cost:=0; NetDot[En].Ne :=0; { 从最后一段开始,由后何前逐步递推 }
{ 以下两句倒推的求解。 }
For J:= N Downto 1 Do
For X:= N Downto J Do
If ( Way[J,X] >0 ) And ( NetDot[X].Cost <> Maxint )
And(Way[J,X]+NetDot[X].Cost<NetDot[J].Cost ) Then
{ 若En至x点的最短路己经求出,且加入边(j,x)后使得En至j的路线目前最短 }
Begin
NetDot[J].Cost:=NetDot[X].Cost+Way[J,X] ;
{ 则记下最短路线长度 }
NetDot[J].Ne:=X;{ jX}
End;
If NetDot[St].Cost=Maxint Then
Writeln('NoWay')
Else
Begin { 否则从起点出发,按计算顺序反推最短路线 }
X:=St;
While X<>0 Do Begin
Write(X:3,' ');
X:=NetDot[X].Ne;
End;
End;
(*read(Str);*)
End.
INPUT:
10 1 10
0 2 5
1 -1 -1
-1 -1 -1 -1
-1 0 -1 -1 12
14 -1 -1
-1 -1
-1 -1 0 -1 6 10 4 -1 -1 -1
-1 -1 -1 0 -1 12
11 -1 -1
-1
-1 -1 -1 -1 0 -1 -1 3 9 -1
-1 -1 -1 -1 -1 0 -1 6 5 -1
-1 -1 -1 -1 -1 -1 0 -1 10 -1
-1 -1 -1 -1 -1 -1 -1 0 -1 5
-1 -1 -1 -1 -1 -1 -1 -1 0 2
-1 -1 -1 -1 -1 -1 -1 -1 -1 0
OUTPUT:
File Name=q01.txt
1 3 5 8 10
动态规划问题的经典实例顺推
(1)问题描述
最优化原理应在子问题求解中体现。有些问题也允许顺推。
给定一个正整数序列a(1),a(2),...,a(n)不改变序列中每个元素在序列中的位置,把它们相加,井用括号记每次加法所得的和,称为中间和。
编程完成任务:
1·添上n-l对括号,加法运算依括号顺序进行,得到n-l个中间和;
2·求出使中间和最少的添括号方法。
例如给出序列是4,1,2,3。
第一种添括号方法:
((4+1)+(2+3))=((5)+(5))=(10),
有三个中间和是5,5,10,它们之和为:5+5+10=20,
第二种添括号方法:(4+((1+2)+3))=(4+((3)+3))=(4+(6))=(10),
中间和是3,6,10,它们之和为19。
(2)算法分折
1·把序列的n个数,看成二元树的叶子(二叉树)的权。如果用一种括号把两个正整数括起来,则它们对应结点的父辈的权就是由这次加法产生的中间和。这样就把对序列任何一种加括号的方法与一棵带权的二元树对应。上面例子中加括号方法对应二元树如图
一棵带权二元树的代价就是树中所有根结点权之和。代价最小的带权二元树称为最优二元树。问题转化为求最优带权二元树。
那么,什么是最优带权二元树呢?
最优二叉树,又称哈夫曼树,是一类带权路径长度最短的树,有着广泛的应用。
我们首先给出路径和路径长度的概念。从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径,路径上的分支数目称做路径长度。树的路径长度是从树根到每一结点的路径长度之和。这种路径长度最短的二叉树是。
若将上述概念推广到一般情况,考虑带权的结点。结点的带权路径长度为从该结点树根之间的路径长度与结点上权的乘积。树的带权路径长度为树中所有叶子结点的带路径长度之和,通常记作
WPL=∑W(k)L(k) k=1...n
假设有n个权值W(1),W(2),......,W(n),试构造一棵有n个叶子结点的二叉树,每个叶子结点带权为W(k),则其中带权路径长度WPL最小的二叉树称做最优二又树或哈夫显树。
例如,图中的两棵二叉树,都有4个叶子结点a、b、c、d,分别带权4,1,2,3,它们的带权路径长度分别为
(a)WPL=4×2十1×2十2×2十3×2=20
(b)WPL=4×1十1×3十2×3十3×2=19
如何构造哈夫曼树呢?俗称哈夫曼算法。现叙述如下:
(1)根据给定的n个权值W(1),W(2),......,W(n)构成n棵二叉树的集合F={T(1),T(2),......,T(n)}
其中每棵二叉树T(k)中只有一个带权为W(k)的根结点,其左右子树均空。
(2)在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左、右子树上根结点的权值之和。
(3)在F中删除这两棵树,同时将新得到的二叉树加入F中。
(4)重复(2)和(3),直到F只含一棵树为止。这棵树便是哈夫曼树。
2·如果一棵带权的二元树是最优的,那末它的任何一棵子树对于这棵子树的树叶来说也是最优的。因此,带权最优二元树的问题符合最优化原则。可以用动态归划方法求解。
3·对于这个问题在决策时要考虑到树叶序列的划分方案。
[状态分析]
1·从序列4,4,8,5,4,3,5分析它的二元权的结构。图给出两棵二元树(a)、(b)。
这棵7片叶叶的二元树,图(a)中的树的左子树是三片叶子,右子树是4片叶子,对应于树叶的一种划分方案。记为(左3,右4)。对应添号形式是:(4+4)+8)+((5+4)+(3+5)))其代价和为91。
图(b)对应于另一种划分方案记为(左4,右3),其代价和是94。可见,由于树叶的划分方案不同,所得的二元树的代价也不同。对七片叶子的二元树,可以有
(左1,右6),(左2,右5),(左3,右4),(左4,右3),(左5,右2),(左6,右1)六种划分方法,对于每一种划分方法都对元一棵二元树,从中找出最优的二元树。
2·决策的前提是最优化原理。对于每一种划分方案,比如(左3,右4),应该知道前三数4,4,8作为树叶构成最优的子二元树是什么,也应知道5,4,3,5作为树叶构成最优二元树的最优子二元树是什么。根据最优化原理,由这棵最优子二元树合成的二元树一定是对于(左3,右4)划分方案最优二元树。从此可见,二元树的构成中每一步的决策不仅与前一步决定有关,而且与若干步的决策有关。二元树的代价计算是一个递推过程。我们选用序列4,4,8,5,4,3,5把它可能构成的各类带权二元树的代价与权的递推计算方法表示如图中。
计算方法说明如下,
[1]一片树叶权重为叶片的权,代价为0,得图的第一层。
[2]二片树叶。它的构成状态共有:(4,4),(4,8),(8,5),(5,4),(4,3),(3,5)共六种。括号内数字之和是中间和即为子树根的权。于是生成2片树叶权重。因为1片树叶权为0,故2片的代价与权重一样。
[3]三片树叶,它的构成状态有:(4,4,8),(4,8,5),(8,5,4),(5,4,3),(4,3,5)共计5种。于是生成二片树叶的权。代价计算时应考虑3片树叶的划分状态可能(左1,右2)或(左2,右1),3片树叶的代价应考虑它们代价中的最小者。若是对(4,4,8)进行划分时其状态有:
(1)以(左1,右2)划分时其状态有(4),(4,8),其中(4)的代价为0,(4,8)代价为12。代价和为12=0+12。
(2)以(左2,右1)划分时其状态有(4,4),(8),其中(4,4)的代价为8,8的代价为0。代价和为8。
因为8比12小,3片树叶4,4,8的代价是它的权加上划分状态中代价小的。于是得出第一个代价为16+8=24。余类推。
[4]四片树叶时,不难算出其权重为21,21,20,17。代价计算应考虑前面划分的可能情况。以树叶权重为4,4,8,5为例。它的划分方法有:
(左1,右3),(左2,右2),(左3,右1)三种。
把每一种划分的代价计算如下:
(1)以(左1,右3)划分状态是:(4),(4,4,5),其中(4)的代价为0,(4,4,5)代价为29,0+29=29(代价和)。
(2)以(左2,右2)划分状态是:(4,4),(8,5),其中(4,4)的代价为8,(8,5)代价为13,8+13二21(代价和)。
(3)以(左3,右1)划分状态是:(4,4,8),(5),其中(4,4,8)的代价24,(5)的代价为0,24十0二24(代价和)。
这三种划分状态代价和最小的是21。于是4片中4,4,8,5的权为21。代价为21+21=42。其他代价可仿此计算。
[5]五片树叶时,权重不难算出。代价计算应考虑划分:
(左1,右4),(左2,右3),(左3,右2),(左4,右1)的代价。
当权重为25,树叶是4,4,8,5,4。
划分(左1,右4)的状态是(4),(4,8,5,4),代价和为0+42=42。
划分(左2,右3)的状态是(4,4),(8,5,4),代价和为8+26=34。
因为8,5,4是三片叶子中第三个,从3片树叶的代价中查到26。
划分(左3,右2)的状态是(4,4,8),(5,4)。由图中查得代价和为24+9=33。
划分(左4,右1)的状态是(4,4,8,5),(4),由图中查得代价和为42+0=42。
于是关于权重为25,树叶为4,4,8,5,4的代价为25+33=58。
当权重为24,树叶为4,8,5,4,3。
划分(左1,右4)代价为0+39=39
划分(左2,右3)代价为12+19=31
划分(左3,右2)代价为29+7=36
划分(左4,右1)代价为42+0=42
31是最小代价,权重为24,于是得代价为31+24=55。
当权重为25,树叶为8,5,4,3,5按划分计算可得其代价为57。
其他的计算由读者去完成。
从以上分析,带权二元树的代价计算是一个比较复杂的递推过程。高层的代价计算,必须用本身的划分状态,利用底层的代价进行计算。虽然递推关系比较复杂,对大多数问题来说,动态规划算法的复杂性有可能是多项式级的。动态规划是一个很有实用价值的用途甚广的组合算法。
(3)程序清单
PROGRAM MIN_COST(INPUT,OUTPUT);
CONST MAXN=100;FNAME='Q31.TXT';
TYPE TLIST=ARRAY[1..MAXN,1..MAXN] OF INTEGER;
VAR B,SUM,DE:TLIST;
N:BYTE;
PROCEDURE INIT;
VAR
I,J:INTEGER;
F:TEXT;
BEGIN
FILLCHAR(B,SIZEOF(B),0);
FILLCHAR(SUM,SIZEOF(SUM),0);
FILLCHAR(DE,SIZEOF(DE),0);
ASSIGN(F,FNAME);
RESET(F);
READLN(F,N);
FOR I:=1 TO N DO
READ(F,SUM[1,I]);
CLOSE(F);
FOR I:=2 TO N DO
FOR J:=1 TO N-I+1 DO
SUM[I,J]:=SUM[I-1,J]+SUM[1,I-1+J];
END;
PROCEDURE MAKE;
VAR
I,J,K,P:BYTE;
O,NO:INTEGER;
BEGIN
FOR I:=2 TO N DO
FOR J:=1 TO N-I+1 DO
BEGIN
P:=0;O:=MAXINT;
FOR K:=1 TO I-1 DO
BEGIN
NO:=SUM[I,J]+B[K,J]+B[I-K,J+K];
IF NO<O THEN
BEGIN
O:=NO;
P:=K;
END;
END;
B[I,J]:=O;
DE[I,J]:=P;
END;
END;
PROCEDURE PRINT;
VAR
I:INTEGER;
A:ARRAY[1..MAXN] OF INTEGER;
X1,X2:INTEGER;
PROCEDURE PA;
VAR I:INTEGER;
BEGIN
IF X1=0 THEN EXIT;
FOR I:=1 TO N DO
IF A[I]<>0 THEN
BEGIN
IF (I=X1) OR (I=X2) THEN WRITE('-');
WRITE(A[I]);
WRITE(' ');
END;
WRITELN;
END;
PROCEDURE SHOW(I,J:INTEGER);
VAR
I1,I2,J1,J2:BYTE;
BEGIN
I1:=DE[I,J];
J1:=J;
I2:=I-I1;
J2:=J+I1;
IF I>2 THEN
BEGIN
SHOW(I1,J1);
SHOW(I2,J2);
END;
X1:=J1;
X2:=J2;
IF X1<>X2 THEN
BEGIN
PA;
A[J1]:=A[J1]+A[J2];
A[J2]:=0;
END;
END; {OF SHOW}
BEGIN
FOR I:=1 TO N DO
A[I]:=SUM[1,I];
SHOW(N,1);
X1:=-10;
X2:=-10;
PA;
WRITELN('MIN COST=',B[N,1]);
END;
BEGIN
INIT;
MAKE;
PRINT;
END.
INPUT:
10
3 18 7 14 10 12 23 41 16 24
OUTPUT:
- 3 - 18 7 14 10 12 23 41 16 24
21 - 7 - 14 10 12 23 41 16 24
- 21 - 21 10 12 23 41 16 24
42 - 10 - 12
23 41 16 24
42 - 22 - 23 41 16 24
- 42 - 45 41 16 24
87 41 - 16 - 24
87 - 41 - 40
- 87 - 81
168
MIN COST = 527