动态规划算法的优化技巧
福州第三中学 毛子青
[关键词] 动态规划、时间复杂度、优化、状态
[摘要]
动态规划是信息学竞赛中一种常用的程序设计方法,本文着重讨论了运用动态规划思想解题时时间效率的优化。全文分为四个部分,首先讨论了动态规划时间效率优化的可行性和必要性,接着给出了动态规划时间复杂度的决定因素,然后分别阐述了对各个决定因素的优化方法,最后总结全文。
[正文]
一、引言
二、动态规划时间复杂度的分析
时间复杂度=状态总数*每个状态转移的状态数*每次状态转移的时间[1]
三、动态规划时间效率的优化
3.1 减少状态总数
设n首歌曲按照写作顺序排序后的长度为long[1..n],则动态规划的状态表示描述为:
g[i, j, k],0≤i≤n,0≤j≤m,0≤k 当k≥long[i],i≥1时: g[i, j, k]=max{g[i-1,j,k-long[i]],g[i-1,j,k]} 当k g[i, j, k]=max{g[i-1,j-1,t-long[i]],g[i-1,j,k]} 规划的边界条件为: 当0≤k 我们来分析上述算法的时间复杂度,上述算法的状态总数为O(n*m*t),每个状态转移的状态数为O(1),每次状态转移的时间为O(1),所以总的时间复杂度为O(n*m*t)。由于n,m,t均不超过20,所以可以满足要求。 [算法优化] 当数据规模较大时,上述算法就无法满足要求,我们来考虑通过改进状态表示提高算法的时间效率。 本题的最优目标是用给定长度的若干张唱片录制尽可能多的歌曲,这实际上等价于在录制给定数量的歌曲时尽可能少地使用唱片。所谓“尽可能少地使用唱片”,就是指使用的完整的唱片数尽可能少,或是在使用的完整的唱片数相同的情况下,另加的分钟数尽可能少。分析可知,在这样的最优目标之下,该问题同样具有最优子结构性质,即:设D在前i首歌中选取j首歌录制的最少唱片使用方案,那么若其中选取了第i首歌,则D-{i}是在前i-1首歌中选取j-1首歌录制的最少唱片使用方案,否则D前i-1首歌中选取j首歌录制的最少唱片使用方案,同样,问题的最优解包含了子问题的最优解。 改进的状态表示描述为: g[i, j]=(a, b),0≤i≤n,0≤j≤i,0≤a≤m,0≤b≤t,表示在前i首歌曲中选取j首录制所需的最少唱片为:a张唱片另加b分钟。由于第i首歌分为发行和不发行两种情况,这样我们可以得到如下的状态转移方程和边界条件: g[i, j]=min{g[i-1,j],g[i-1,j-1]+long[i]} 其中(a, b)+long[i]=(a’, b’)的计算方法为: 当long[i]≤t-b时: a’=a; b’=b+long[i]; 当long[i]>t-b时: a’=a+1; b’=long[i]; 规划的边界条件: g[i,0]=(0,0) 0≤i≤n 这样题目所求的最大值是:ans=max{k| g[n, k]≤(m-1,t)} 改进后的算法,状态总数为O(n2),每个状态转移的状态数为O(1),每次状态转移的时间为O(1),所以总的时间复杂度为O(n2)。值得注意的是,算法的空间复杂度也由改进前的O(m*n*t)降至优化后的O(n2)。 (程序及优化前后的运行结果比较见附件) 通过对本题的优化,我们认识到:应用不同的状态表示方法设计出的动态规划算法的性能也迥然不同。改进状态表示可以减少状态总数,进而降低算法的时间复杂度。在降低算法的时间复杂度的同时,也降低了算法的空间复杂度。因此,减少状态总数在动态规划的优化中占有重要的地位。 2、选择适当的规划方向 动态规划方法的实现中,规划方向的选择主要有两种:顺推和逆推。在有些情况下,选取不同的规划方向,程序的时间效率也有所不同。一般地,若初始状态确定,目标状态不确定,则应考虑采用顺推,反之,若目标状态确定,而初始状态不确定,就应该考虑采用逆推。那么,若是初始状态和目标状态都已确定,一般情况下顺推和逆推都可以选用,但是,能否考虑选用双向规划呢? 双向搜索的方法已为大家所熟知,它的主要思想是:在状态空间十分庞大,而初始状态和目标状态又都已确定的情况下,由于扩展的状态量是指数级增长的,于是为了减少状态的规模,分别从初始状态和目标状态两个方向进行扩展,并在两者的交汇处得到问题的解。 上述优化思想能否也应用到动态规划之中呢?来看下面这个例子。 例二、 Divide (Merc`2000) [问题描述] 有价值分别为1..6的大理石各a[1..6]块,现要将它们分成两部分,使得两部分价值和相等,问是否可以实现。其中大理石的总数不超过20000。(英文试题详见附件) [算法分析] 令S=∑(i*a[i]),若S为奇数,则不可能实现,否则令Mid=S/2,则问题转化为能否从给定的大理石中选取部分大理石,使其价值和为Mid。 这实际上是母函数问题,用动态规划求解也是等价的。 m[i, j],0≤i≤6,0≤j≤Mid,表示能否从价值为1..i的大理石中选出部分大理石,使其价值和为j,若能,则用true表示,否则用false表示。则状态转移方程为: m[i, j]=m[i, j] OR m[i-1,j-i*k] (0≤k≤a[i]) 规划的边界条件为:m[i,0]=true; 0≤i≤6 若m[i, Mid]=true,0≤i≤6,则可以实现题目要求,否则不可能实现。 我们来分析上述算法的时间性能,上述算法中每个状态可能转移的状态数为a[i],每次状态转移的时间为O(1),而状态总数是所有值为true的状态的总数,实际上就是母函数中项的数目。 [算法优化] 实践发现:本题在i较小时,由于可选取的大理石的价值品种单一,数量也较少,因此值为true的状态也较少,但随着i的增大,大理石价值品种和数量的增多,值为true的状态也急剧增多,使得规划过程的速度减慢,影响了算法的时间效率。 另一方面,我们注意到我们关心的仅是能否得到价值和为Mid的值为true的状态,那么,我们能否从两个方向分别进行规划,分别求出从价值为1..3的大理石中选出部分大理石所能获得的所有价值和,和从价值为4..6的大理石中选出部分大理石所能获得的所有价值和。最后通过判断两者中是否存在和为Mid的价值和,由此,可以得出问题的解。 状态转移方程改进为: 当i≤3时: m[i, j]=m[i, j] OR m[i-1,j-i*k] (1≤k≤a[i]) 当i>3时: m[i, j]=m[i, j] OR m[i+1,j-i*k] (1≤k≤a[i]) 规划的边界条件为:m[i,0]=true; 0≤i≤7 这样,若存在k,使得m[3,k]=true, m[4,Mid-k]=true,则可以实现题目要求,否则无法实现。 (程序及优化前后的运行结果比较见附件) 从上图可以看出双向动态规划与单向动态规划在计算的状态总数上的差异。 3.2 减少每个状态转移的状态数 1、四边形不等式和决策的单调性 例三、石子合并问题(NOI`95) m[i,j]=0 i=j i<j 同时令s[i,j]=k,表示合并的断开位置,便于在计算出最优值后构造出最优解。 上式中的计算,可在预处理时计算,i=1..n;t[0]=0, 则: 上述算法的状态总数为O(n2),每个状态转移的状态数为O(n),每次状态转移的时间为O(1),所以总的时间复杂度为O(n3)。 [算法优化] 当函数w[i,j]满足时,称w满足四边形不等式[2]。 当函数w[i,j]满足w[i’,j]≤w[i,j’] 时称w关于区间包含关系单调。 在石子归并问题中,令w[i,j]= ,则w[i,j]满足四边形不等式,同时由d[i]≥0,t[i]≥0可知w[i,j]满足单调性。 m[i,j]=0 i=j i 我们对四边形不等式中“长度”l=j’-i进行归纳: 当i=i’或j=j’时,不等式显然成立。由此可知,当l≤1时,函数m满足四边形不等式。 下面分两种情形进行归纳证明: 情形1:i 在这种情形下,四边形不等式简化为如下的反三角不等式:m[i,j]+m[j,j’] ≤m[i,j’],设k=max{p | m[i,j’]=m[i,p-1]+m[p,j’]+w[i,j’] },再分两种情形k≤j或k>j。下面只讨论k≤j,k>j的情况是类似的。 情形1.1:k≤j,此时: 情形2:i 设 y=max{p | m[i’,j]=m[i’,p-1]+m[p,j]+w[i’,j] } z=max{p | m[i,j’]=m[i,p-1]+m[p,j’]+w[i,j’] } 仍需再分两种情形讨论,即z≤y或z>y。下面只讨论z≤y,z>y的情况是类似的。 由i 综上所述,m[i,j]满足四边形不等式。 令s[i,j]=max{k | m[i,j]=m[i,k-1]+m[k,j]+w[i,j] } 由函数m[i,j]满足四边形不等式可以推出函数s[i,j]的单调性,即 s[i,j]≤s[i,j+1]≤s[i+1,j+1], i≤j 当i=j时,单调性显然成立。因此下面只讨论i 令mk[i,j]=m[i,k-1]+m[k,j]+w[i,j]。要证明s[i,j]≤s[i,j+1],只要证明对于所有i 事实上,我们可以证明一个更强的不等式 mk[i,j]-mk’[i,j]≤mk[i,j+1]-mk’[i,j+1] 也就是: mk[i,j]+mk’[i,j+1]≤mk[i,j+1]+mk’[i,j] 利用递推定义式将其展开整理可得:m[k,j]+m[k’,j+1]≤m[k’,j]+m[k,j+1],这正是k≤k’≤j 综上所述,当w满足四边形不等式时,函数s[i,j]具有单调性。 于是,我们利用s[i,j]的单调性,得到优化的状态转移方程为: m[i,j]=0 i=j i 用类似的方法可以证明,对于最大得分问题,也可采用同样的优化方法。 改进后的状态转移方程所需的计算时间为 (程序及优化前后的运行结果比较见附件) 上述方法利用四边形不等式推出最优决策的单调性,从而减少每个状态转移的状态数,降低算法的时间复杂度。 上述方法是具有普遍性的。对于状态转移方程与①式类似,且w[i,j]满足四边形不等式的动态规划问题,都可以采用相同的优化方法,如最优二叉排序树(NOI`96)等。下面再举一例。 例四、邮局(IOI`2000) [问题描述] 接着,我们用数学归纳法证明函数m也满足四边形不等式。对四边形不等式中“长度”l=j’-i进行归纳: 当i=i’或j=j’时,不等式显然成立。由此可知,当l≤1时,函数m满足四边形不等式。 下面分两种情形进行归纳证明: 情形1:i
设k=max{p | m[i,j’]=m[i,p-1]+m[p,j’]+w[i,j’] },再分两种情形k≤j或k>j。下面只讨论k≤j,k>j的情况是类似的。 情形2:i 设 y=max{p | m[i’,j]=m[i’-1,p]+w[p+1,j] } z=max{p | m[i,j’]=m[i-1,p]+w[p+1,j’] } 我们先来证明s[i-1,j]≤s[i,j],只要证明对于所有i≤k 类似地,我们可以证明一个更强的不等式 mk[i-1,j]-mk’[i-1,j]≤mk[i,j]-mk’[i,j] 也就是: mk[i-1,j]+mk’[i,j]≤mk[i,j]+mk’[i-1,j] (程序及优化前后的运行结果比较见附件) m[i,j]=0 i=j i<j
m[i,j]=0 i=j i<j 优化后每个状态转移的状态数减少为O(1),算法总的时间复杂度也降为O(n2)。 (程序及优化前后的运行结果比较见附件) 3、合理组织状态 在动态规划求解的过程中,需要不断地引用已经计算过的状态。因此,合理地组织已经计算出的状态有利于提高动态规划的时间效率。 这也是一道动态规划的经典应用。动态规划的状态表示描述为: m[i],1≤i≤n,表示以x[i]结尾的最长上升子序列的长度,则问题的解为 max{m[i],1≤i≤n},状态转移方程和边界条件为: m[i]=1+max{0, m[k]|x[k] 同时当m[i]>1时,令p[i]=k,表示最优决策,以便在计算出最优值后构造最长单调上升子序列。 上述算法的状态总数为O(n),每个状态转移的状态数最多为O(n),每次状态转移的时间为O(1),所以算法总的时间复杂度为O(n2)。 [算法优化] 我们先来考虑以下两种情况: 1、若x[i] 由此可见,在所有状态值相同的状态中,只需保留最后一个元素值最小的那个状态即可。 2、若x[i] 综合上述两点,我们得出了状态m[k]需要保留的必要条件:不存在i使得:x[i] 也就是说,设当前保留的状态集合为S,则S具有以下性质D: 对于任意i∈S, j∈S, i≠j有:m[i]≠m[j],且若m[i] 下面我们来考虑状态转移:假设当前已求出m[1..i-1],当前保留的状态集合为S,下面计算m[i]。 1、若存在状态k∈S,使得x[k]=x[i],则状态m[i]必定不需保留,不必计算。因为,不妨设m[i]=m[j]+1,则x[j] 2、否则,m[i]=1+max{m[j]| x[j] 3、若2成立,则我们往S中增加了一个状态,为了保持S的性质,我们要对S进行维护,若存在状态k∈S,使得m[i]=m[k],则我们有x[i] 于是,我们得到了改进后的算法: For i:=1 to n do { 找出集合S中的x值不超过x[i]的最大元素k; if x[k] { m[i]:=m[k]+1; 将状态i插入集合S; 找出集合S中的x值大于x[i]的最小元素j; if m[j]=m[i] then 将状态j从S中删去; } } 从性质D和算法描述可以发现,S实际上是以x值为关键字(也是以m值为关键字)的有序集合。若使用平衡树实现有序集合S,则该算法的时间复杂度为O(n*log2n)。本题优化后,每个状态转移的状态数仅为O(1),而每次状态转移的时间变为O(log2n),这也体现了上文所提到的优化中不同因素之间的矛盾,但从总体上看,算法的时间复杂度是降低了。 (程序及优化前后的运行结果比较见附件) 回顾本题的优化过程,首先通过分析状态之间的分析,减少需要保留的状态数,同时发现需要保留状态的单调性,从而减少了每个状态可能转移的状态数,并通过高效数据结构平衡树组织当前保留的状态,实现算法的优化。 通过对本题的优化,我们认识到减少保留的状态数,合理组织已经计算出的状态可以实现减少每个状态可能转移的状态数,同时,选取恰当的数据结构也是算法优化的一个重要原则,在下文的阐述中,还会看到借助数据结构实现算法优化。 4、细化状态转移 所谓“细化状态转移”,就是将原来的一次状态转移细化成若干次状态转移,其目的在于减少总的状态转移的次数。在优化前,问题的决策一般都是复合决策,也就是一些子决策的排列,因此决策的规模较大,每个状态可能转移的状态数也就较多,优化的方法就是将每个复合决策细化成若干个子决策,并在每个子决策后面增设一个状态,这样,后面的子决策只在前面的子决策达到最优解时才进行转移,因此在优化后,虽然,状态总数增加了,但是总的状态转移次数却减少了,算法总的复杂度也就降低了。 应该注意的是:上述优化应该满足一个条件,即原来每个复合决策的各个子决策之间也满足最优化原理和无后效性,也就是说:复合最优决策的子决策也是最优决策;前面的子决策不影响后面的子决策。 上述优化方法再一次体现了实现一个因素的优化要以增大另一个因素作为代价,但是,算法总的时间复杂度的降低才是我们的真正目的。 1、减少决策时间 F(v,i):表示L前i个字符划分为以动词结尾(当i<>M时,可带任意个辅词后缀)的最优分解方案下划分的句子数与单词数; F(u,i):表示L前i个字符划分为以名词结尾(当i<>M时,可带任意个辅词后缀)的最优分解方案下划分的句子数与单词数。 过去的分解方案仅通过最后一个非辅词的词性影响以后的决策,所以这种状态表示满足无后效性, 状态转移方程为: F(v,i)=min{ F(n,j)+(0,1), L(j+1..i)为动词; F(v,j)+(0,1), L(j+1..i)为辅词,i<>M;} F(n,i)=min{ F(n,j)+(1,1), L(j+1..i)为名词; F(v,j)+(0,1), L(j+1..i)为名词; F(n,j)+(0,1), L(j+1..i)为辅词,i<>M;} 边界条件:F(v,0)=(1,0); F(n,0)=(∞, ∞); 问题的解为:min{ F(v,M), F(u,M) }; 上述算法中,状态总数为O(M),每个状态转移的状态数最多为20,在进行状态转移时,需要查找L[j+1..i]的词性,根据其词性做出相应的决策,并引用相应的状态。下面就通过不同的方法查找L[j+1..i]的词性,比较它们的时间复杂度。 [算法实现] 设单词表的规模为N,首先我们对单词表进行预处理,将单词按字典顺序排序并合并具有多重词性的单词。在查找词性时有以下几种方法: 方法1、采用顺序查找法。最坏情况下需要遍历整个单词表,因此最坏情况下的时间复杂度为O(20*N*M),比较次数最多可达1000*5k*20=108,当数据量较大时效率较低。 方法2、采用二分查找法。最坏情况下的时间复杂度为O(20*M*log2N),最多比较次数降为5k*20*log21000=106,完全可以忍受。 集合查找最为有效的方法要属采用哈希表了。 方法3、采用哈希表查找单词的词性。首先将字符串每四位折叠相加计算关键值k,然后用双重哈希法计算哈希函数值h(k)。采用这种方法,通过O(N)时间的预处理构造哈希表,每次查找只需O(1)的时间,因此,算法的时间复杂度为O(20*M+N)=O(M)。 采用哈希表是进行集合查找的一般方法,对于以字符串为元素的集合还有更为高效的方法,即采用检索树[3]。 方法四、采用检索树查找单词的词性。由于每个状态在进行状态转移时需要查找的所有单词都是分布在同一条从树根到叶子的路径上的,因此,如果选取从树根走一条路径到叶子作为基本操作,则每个状态进行状态转移时的最多20次单词查找只需O(1)的时间,另外,建立检索树需要O(N)的时间,因此,算法总的时间复杂度虽然仍为O(M),但是由于时间复杂度的常数因子小于方法三,因此实际测试的速度也最快。 2、减少计算递推式的时间 四、结语 参考文献 [1] 吴文虎、 赵鹏,1993-1996美国计算机程序设计竞赛试题与解析,清华大学出版社,1999。 [2] 吴文虎、王建德,国际国内青少年信息学(计算机)竞赛试题解析,清华大学出版社,1997。 [3] 傅清祥、王晓东,算法与数据结构,电子工业出版社,1998。 [4] 全国青少年信息学(计算机)奥林匹克分区联赛组织委员会,信息学奥林匹克(季刊),1999.3,2000.2。