第6讲动态规划专题讲座文档格式.docx
《第6讲动态规划专题讲座文档格式.docx》由会员分享,可在线阅读,更多相关《第6讲动态规划专题讲座文档格式.docx(46页珍藏版)》请在冰豆网上搜索。
在7313中插入1个乘号使乘积最大,插入方式为731*3;
在3926中插入2个乘号使乘积最大,插入方式为3*92*6。
这些子问题的最优解,这就是最优子结构特性。
最优性原理是动态规划的基础。
任何一个问题,如果失去了这个最优性原理的支持,就不可能用动态规划设计求解。
能采用动态规划求解的问题都需要满足以下条件:
(1)问题中的状态必须满足最优性原理;
(2)问题中的状态必须满足无后效性。
所谓无后效性是指:
“下一时刻的状态只与当前状态有关,而和当前状态之前的状态无关,当前状态是对以往决策的总结”。
6.1.2动态规划实施步骤
动态规划求解最优化问题,通常按以下几个步骤进行。
(1)把所求最优化问题分成若干个阶段,找出最优解的性质,并刻划其结构特性。
最优子结构特性是动态规划求解问题的必要条件,只有满足最优子结构特性的多阶段决策问题才能应用动态规划设计求解。
(2)将问题发展到各个阶段时所处不同的状态表示出来,确定各个阶段状态之间的递推(或递归)关系,并确定初始(边界)条件。
通过设置相应的数组表示各个阶段的最优值,分析归纳出各个阶段状态之间的转移关系,是应用动态规划设计求解的关键。
(3)应用递推(或递归)求解最优值。
递推(或递归)计算最优值是动态规划算法的实施过程。
具体应用与所设置的表示各个阶段最优值的数组密切相关。
(4)根据计算最优值时所得到的信息,构造最优解。
构造最优解就是具体求出最优决策序列。
通常在计算最优值时,根据问题具体实际记录更多的信息,根据所记录的信息构造出问题的最优解。
以上步骤前3个是动态规划设计求解最优化问题的基本步骤。
当只需求解最优值时,第4个步骤可以省略。
若需求出问题的最优解,则必须执行第4个步骤。
6.2最长子序列探索
本节应用动态规划探索两个典型的子序列问题:
最长非降子序列与两个序列的最长公共子序列。
6.2.1最长非降子序列
1.案例提出
给定一个由n个正整数组成的序列,从该序列中删除若干个整数,使剩下的整数组成非降子序列,求最长的非降子序列。
例如,由12个正整数组成的序列为:
48,16,45,47,52,46,36,28,46,69,14,42
请在序列中删除若干项,使剩下的项为非降(即后面的项不小于后面的项)序列,剩下的非降序列最长为多少项?
2.递推实现动态规划设计
设序列的各项为a[1],a[2],…,a[n](可随机产生,也可从键盘依次输入),对每一个整数操作为一个阶段,共为n个阶段。
(1)建立递推关系
设置b数组,b[i]表示序列的第i个数(保留第i个数)到第n个数中的最长非降子序列的长度,i=1,2,…,n。
对所有的j>
i,比较当a[j]≥a[i]时的b[j]的最大值,显然b[i]为这一最大值加1,表示加上a[i]本身这一项。
因而有递推关系:
b[i]=max(b[j])+1(a[j]≥a[i],1≤i<
j≤n)
边界条件:
b[n]=1
(2)逆推计算最优值
b[n]=1;
for(i=n−1;
i>
=1;
i−−)
{max=0;
for(j=i+1;
j<
=n;
j++)
if(a[i]<
=a[j]&
&
b[j]>
max)
max=b[j];
b[i]=max+1;
//逆推得b[i]
}
逆推依次求得b[n−1],…,b[1],比较这n−1个值得其中的最大值lmax,即为所求的最长非降子序列的长度即最优值。
(3)构造最优解
从序列的第1项开始,依次输出b[i]分别等于lmax,lmax−1,…,1的项a[i],这就是所求的一个最长非降子序列。
(4)递推实现动态规划程序设计
//递推实现动态规划
#include<
stdio.h>
stdlib.h>
time.h>
voidmain()
{inti,j,n,t,x,max,lmax,a[2000],b[2000];
t=time(0)%1000;
srand(t);
//随机数发生器初始化
printf("
inputn(n<
2000):
"
);
scanf("
%d"
&
n);
for(i=1;
i<
i++)
{a[i]=rand()%(5*n)+10;
//产生并输出n个数组成的序列
%d"
a[i]);
}
b[n]=1;
lmax=0;
for(i=n-1;
i--)//逆推求最优值lmax
{max=0;
for(j=i+1;
if(a[i]<
max=b[j];
b[i]=max+1;
//逆推得b[i]
if(b[i]>
lmax)lmax=b[i];
//比较得最大非降序列长
\nL=%d.\n"
lmax);
//输出最大非降序列长
x=lmax;
if(b[i]==x)
{printf("
x--;
}//输出一个最大非降序列
}
3.递归实现动态规划设计
(1)建立递归关系
设q(i)表示序列的第i个数(保留第i个数)到第n个数中的最长非降子序列的长度,i=1,2,…,n。
i,比较当a[j]≥a[i]时的q(j)的最大值,显然q(i)为这一最大值加1,表示加上a[i]本身这一项。
因而有递归关系:
q(i)=max(q(j))+1(a[j]≥a[i],1≤i<
递归出口:
q(n)=1
(2)递归函数设计
intq(inti)
{intj,f,max;
if(i==n)f=1;
else
{max=0;
q(j)>
max=q(j);
f=max+1;
returnf;
(3)在主函数中依次调用q(n−1),…,q
(1),比较这n−1个值得其中的最大值lmax,即为所求的最长非降子序列的长度即最优值。
(4)构造最优解
从序列的第1项开始,依次输出q(i)分别等于lmax,lmax−1,…,1所那就的项a[i],这就是所求的一个最长非降子序列。
(5)递归实现动态规划程序设计
//递归实现动态规划
inti,n,a[2000];
{intt,x,lmax;
intq(inti);
lmax=0;
i--)
if(q(i)>
lmax)lmax=q(i);
if(q(i)==x)
4.程序运行示例与讨论
运行程序,inputn(n<
12
481645475246362846691442
L=5.
1645475269
注意,所给序列长度为5的非降子序列可能有多个,这里只输出其中一个。
由上可知,在动态规划设计中,最优值可经递推得到,也可经递归得到。
一般地,应用递推效率更高些,以下各案例的动态规划设计中均应用递推得最优值。
6.2.2最长公共子序列
一个给定序列的子序列是在该序列中删去若干个元素后所得到的序列。
用数学语言表述,
给定序列
,另一序列
,X的子序列是指存在一个严格递增
下标序列
使得对于所有j=1,2,…,k有
。
例如,序列
是序列
的一个子序列,或按紧凑格式书写,序列“bdca”是“abcdcba”的一个子序列。
若序列Z是序列X的子序列,又是序列Y的子序列,则称Z是序列X与Y的公共子序列。
例如,序列“bcba”是“abcbdab”与“bdcaba”的公共子序列。
给定两个序列
和
,找出序列X和Y的最长
公共子序列。
例如,给出序列X:
hsbafdreghsbacdba与序列Y:
acdbegshbdrabsa,这两个序列的最长公共子序列如何求得?
2.动态规划算法设计
求序列X与Y的最长公共子序列可以使用穷举法:
列出X的所有子序列,检查X的每一个子序列是否也是Y的子序列,并记录其中公共子序列的长度,通过比较最终求得X与Y的最长公共子序列。
对于一个长度为m的序列X,其每一个子序列对应于下标集{1,2,…,m}的一个子集,即X的子序列数目多达2m个。
由此可见应用穷举法求解是指数时间的。
最长公共子序列问题具有最优子结构性质,应用动态规划设计求解。
设序列
的最长公共子序列为
,
与
(i=0,1,…,m;
j=0,1,…,n)的最长公共子序列的长度为c(i,j)。
若i=m+1或j=n+1,此时为空序列,c(i,j)=0(边界条件)。
若x
(1)=y
(1),则有z
(1)=x
(1),c(1,1)=c(2,2)+1(其中1为z
(1)这一项);
若x
(1)≠y
(1),则c(1,1)取c(2,1)与c(1,2)中的最大者。
一般地,若x(i)=y(j),则c(i,j)=c(i+1,j+1)+1;
若x(i)≠y(j),则c(i,j)=max(c(i+1,j),c(i,j+1))。
因而归纳为递推关系:
c(i,j)=0(i=m+1或j=n+1)
根据以上递推关系,逆推计算最优值c(0,0)流程为:
for(i=0;
=m;
i++)c[i][n]=0;
//赋初始值
for(j=0;
j++)c[m][j]=0;
for(i=m−1;
=0;
i−−)//计算最优值
for(j=n−1;
j>
j−−)
if(x[i]==y[j])
c[i][j]=c[i+1][j+1]+1;
elseif(c[i][j+1]>
c[i+1][j])
c[i][j]=c[i][j+1];
elsec[i][j]=c[i+1][j];
最长公共子串的长度为:
c[0][0]);
//输出最优值
以上算法时间复杂度为O(mn)。
为构造最优解,即具体求出最长公共子序列,设置数组s(i,j),当x(i)=y(j)时s(i,j)=1;
当x(i)≠y(j)时s(i,j)=0。
X序列的每一项与Y序列的每一项逐一比较,根据s(i,j)与c(i,j)取值具体构造最长公共子序列。
实施x(i)与y(j)比较,其中i=0,1,…,m−1;
j=t,1,…,n−1;
变量t从0开始取值,当确定最长公共子序列一项时,t=j+1。
这样处理可避免重复取项。
若s(i,j)=1且c(i,j)=c(0,0)时,取x(i)为最长公共子序列的第1项;
随后,若s(i,j)=1且c(i,j)=c(0,0)−1时,取x(i)最长公共子序列的第2项;
一般地,若s(i,j)=1且c(i,j)=c(0,0)−w时(w从0开始,每确定最长公共子序列的一项,w增1),取x(i)最长公共子序列的第w项。
构造最长公共子序列描述:
for(t=0,w=0,i=0;
=m−1;
for(j=t;
=n−1;
if(s[i][j]==1&
c[i][j]==c[0][0]−w)
%c"
x[i]);
w++;
t=j+1;
break;
(4)算法的复杂度分析
以上动态规划算法的时间复杂度为O(n2)。
3.最长公共子序列C程序实现
//最长公共子序列
#defineN100
{charx[N],y[N];
inti,j,m,n,t,w,c[N][N],s[N][N];
请输入序列x:
//先后输入序列
%s"
x);
请输入序列y:
y);
for(m=0,i=0;
x[i]!
='
\0'
;
i++)m++;
for(n=0,i=0;
y[i]!
i++)n++;
for(i=0;
//赋边界值
i−−)//递推计算最优值
{c[i][j]=c[i+1][j+1]+1;
s[i][j]=1;
else
{s[i][j]=0;
if(c[i][j+1]>
最长公共子序列的长度为:
//输出最优值
\n最长公共子序列为:
//构造最优解
t=0;
w=0;
for(j=t;
\n"
运行程序示例:
hsbafdreghsbacdba
acdbegshbdrabsa
9
最长公共子序列为:
adeghbaba
6.3最优路径搜索
本节应用动态规划探讨两类最优路径搜索问题,一类是点数值路径,即连接成路径的每一个点都带有一个数值;
另一类是边数值路径,即连接成路径的每一条边都带有一个数值。
6.3.1点数值三角形的最优路径搜索
点数值三角形是一个二维数阵:
三角形由n行构成,第k行有k个点,每一个点都带有一个数值数。
点数值三角形的数值可以随机产生,也可从键盘输入。
最优路径通常由路径所经各点的数值和来确定。
在一个n行的点数值三角形中,寻找从顶点开始每一步可沿左斜(L)或右斜(R)向下至底的一条路径,使该路径所经过的点的数值和最小。
图6.17行点数值三角形
例如,n=7时给出的点数值三角形如图6.1所示,如何寻找从项到底的数值和最小路径?
该最优路径的数值和为多少?
设点数值三角形的数值存储在二维数组a(n,n)。
设数组b(i,j)为点(i,j)到底的最小数值和,字符数组stm(i,j)指明点(i,j)向左或向右的路标。
b(i,j)与stm(i,j)(i=n−1,…,2,1)的值由b数组的第i+1行的第j个元素与第j+1个元素值的大小比较决定,即有递推关系:
b(i,j)=a(i,j)+b(i+1,j+1);
stm(i,j)=“R”(b(i+1,j+1)<
b(i+1,j))
b(i,j)=a(i,j)+b(i+1,j);
stm(i,j)=“L”(b(i+1,j+1)≥b(i+1,j))
其中i=n−1,…,2,1
b(n,j)=a(n,j),j=1,2,…,n。
所求的最小路径数值和即问题的最优值为b(1,1)。
for(j=1;
j++)b[n][j]=a[n][j];
for(i=n−1;
i−−)//逆推得b[i][j]
=i;
if(b[i+1][j+1]<
b[i+1][j])
{b[i][j]=a[i][j]+b[i+1][j+1];
stm[i][j]='
R'
else
{b[i][j]=a[i][j]+b[i+1][j];
L'
Printf("
b(1,1));
为了确定与并输出最小路径,利用stm数组从上而下查找:
先打印a(1,1),若stm(1,1)=“R”,则下一个打印a(2,2),否则打印a(2,1)。
一般地,在输出i循环(i=2,3,…,n)中:
若stm(i−1,j)=“R”则打印“−R−”;
a(i,j+1);
同时赋值j=j+1。
若stm(i−1,j)=“L”则打印“−L−”;
a(i,j);
依此打印出最小路径,即所求的最优解.
以上动态规划算法的时间复杂度为O(n2),空间复杂度也为O(n2)。
2.最小路径寻求与测试程序设计
//点数值三角形的最小路径
{intn,i,j,t;
inta[50][50],b[50][50];
charstm[50][50];
请输入数字三角形的行数n:
srand(t);
//随机数发生器初始化
n;
i++)j=rand();
{for(j=1;
=36-2*i;
j++)printf("
"
for(j=1;
{a[i][j]=rand()/1000+1;
%4d"
a[i][j]);
//产生并打印n行数字三角形
请在以上点数值三角形中从顶开始每步可左斜或右斜至底"
寻找一条数字和最小的路径.\n"
j++)b[n][j]=a[n][j];
i--)//逆推得b[i][j]
else{b[i][j]=a[i][j]+b[i+1][j];
最小路径和为:
%d\n"
b[1][1]);
//输出最小数字和
最小路径为:
a[1][1]);
j=1;
//输出和最小的路径
for(i=2;
if(stm[i-1][j]=='
)
{printf("
-R-%d"
a[i][j+1]);
j++;
-L-%d"
a[