面分析Codes问题时,已经提到了这个优化思想。下面我们从有向无环图的性质入手,进行动态规划。
一、问题描述
一个词是由至少一个至多75个小写英文字母(a,b,…,z)组成的。当在一张一个或多个词组成的表(list)中,每一个词(除第一个)都能由在其前一个词的词尾添加一个或多个字母而得到的话,则称此表为一个词链。
例如:表
i
in
int
integer
为一个含四个词的链,而表
input
integer
不是链.注意:所有仅含一个词的表都是链
输入:你将从文件中读到一张表.。 表中至少有1个词,而所有词所含字母个数之总和不超过2000000个。文件以“.”结束。其余各行每行含一个词。各行已按字典顺序由小到大排序。文件中的词不会有重复。
输出:一个链的长度是指该链所含词的个数。你的程序应从输入文件中找出最长的链,并把该链写入文件中去。一个词占一行。若有多个最长链,只须输出其中任何一个。
输入输出举例;
下图给出了一个由7歌词的输入文件和一个对应的输出文件。输入文件中有一个长度为4的链,这里已没有比它更长的链了。
Input.Txt |
Output.txt |
I If In Input Integer Output . |
i in int integer
|
二、分析
容易看出,这道题可以使用动态规划来解决。但由于题目中的数据可能很大(2M),无论在时间上还是空间上,简单的动态规划都无法满足要求。因此,必须根据题目的特点和本题动态规划的特殊模型,对动态规划算法进行改进。
我们先看看一般的动态规划的模型:将每一个读入的单词看成一个结点。若单词i是单词j的前缀并且i≠j(即单词j可以由单词i的词尾添上一个或多个字母组成),则在i,j之间连一条有向边,方向由i至j。再在图中添加一个始点和终点,始点到任何单词有一条有向边,任何单词到终点有一条有向边,且所有边的权为1。 则题目即要求从始点到终点的一条最长路径。易证上图是有向无环图。因此,求最长路径可以用动态规划。
由于输入文件已经按照字典顺序排序,我们可以先按照单词读入的次序给单词编号。即第一个单词编号为1,第二个单词编号为2,以此类推,且始点编号为0。为了便于叙述,设编号为i的单词为Wi,且记由始点到Wi的最长路径的长度为Li。
这时,动态规划方程为:
初始:L0=0。
方程:Li=Max{Lj+1|Wj是Wi的前缀,j=0..单词总数}。
目标:Result=Max{Li|i=1..单词总数}。
算法:
for i:=1 to n do
for j:=1 to n do
if Wi是Wj的前缀and Li<Lj+1 then Li:=Lj+1
空间复杂度:n=2M=2*106。
时间复杂度:n2=4*1012。
为了优化方程,首先引出以下定理:
定理1:若Wi是Wj的前缀,且i≠j则i<j。
证明:根据字典顺序的定义易证(此略)。
根据定理1,原动态规划方程可改进为:Li=Max{Lj+1| Wj是Wi的前缀,j=0..i-1}。
定理2:若Wi是Wk的前缀,Wj是Wk的前缀,且j>i,则Wi是Wj的前缀。
证明:根据前缀的定义易得,Wi,Wj都是Wk尾部删去某些字符而成,j>i则说明Wj的长度比Wi的大,故Wi是Wj的前缀。
定理3:若Wi是Wk的前缀,且不存在j,j>i且Wj是Wk的前缀,则Lk=Li+1。
证明:若Lk=Lj+1,且Wj是Wk的前缀,j<i,则由定理2,Wj是Wi的前缀。所以Li>Lj,因此,Li+1>Lj+1=Lk,这与Lk=Max{Li+1| Wi是Wk的前缀,i=0..k-1}矛盾。
定理3说明,原动态规划方程可改进为:Li=Lj+1,j为满足Wj是Wi的最长前缀。
定理4:若i<j<k,且Wi不是Wj的前缀,则Wi不是Wk的前缀。
证明:若Wi是Wk的前缀,根据字典顺序的定义,因为Wi<Wj<Wk,所以Wi是Wj的前缀,这与前提Wi不是Wj的前缀相矛盾。因此,Wi不是Wk的前缀。
由定理1和定理4可得定理5:Wk的前缀必定是Wk-1的前缀,或Wk-1。
综合定理3和定理5,我们可以得出优化后的动态规划方程:
若Wi-1是Wi的前缀,则Li=Li-1+1,否则Li=Lj+1,j满足Wj是Wi-1的最长的前缀。
由于一个单词的长度最大为75个字符,因此前缀最多只可能有75个。这时,可以建立一个可能前缀集合来完成整个规划过程。具体算法如下:
begin
可能前缀集合:={W0};
L0=0;
while not结束do begin
Readln (Wi);
j:=Select; {j满足Wj是可能前缀集合中最长的Wi的前缀}
Li=Lj+1;
删除可能前缀集合中所有不是Wi前缀的单词;
在可能前缀集合中添入Wi
end
end.
改进后的算法时空复杂度都大大的降低。但在实现的过程当中,仍然存在优化的余地。特别是对于“可能前缀集合”的数据结构的设计上。若简单的定义数组=array[1..75] of string[75];无论是添加,删除元素还是寻找最长的前缀都不方便,空间也很浪费。由定理2,“可能前缀集合”中任意两个不同的单词都存在前缀关系,任意一个非最长的单词都是最长单词的前缀。因此,我们只要保存最长的单词(即Wi),就可以通过标志分点的方法来表示“可能前缀集合”了。这时,时间复杂度降为O(N),N为文件的的大小≤106,空间复杂度降为O(75)=O(1)。
由于题目要求输出所有解,因此我们可以采取两遍读文件的方法解决。第一遍找到最长词链的长度,第二遍再输出所有解。也可以使用Rewrite过程解决这个问题。具体程序见算法部分。
三、参考程序
TNode=record
Ch: Char; L: Integer
end;
TSet=array[0..75] of TNode;{可能前缀集合}
begin
l=0;{可能前缀集合中单词的最大长度}
MaxL=0;{最长的词链的长度}
Readln(Str);{读入一个单词}
while Str≠’.’ do begin{没有结束}
i:=1;
if l>Length(Str) then l:=Length(Str);
while (i≤l) and (Str[i]=List[i].Ch) do Inc(i);
x:=List[i-1].L;
while i≤Length(Str) do begin{将Str加入可能前缀集合中}
List [i]. Ch: =Str [i]; List [i]. L: =x; Inc (i)
end;
l:=i-1;List[l].L:=x+1;{优化后的动态规划方程}
if List[l].L>MaxL then begin{找到更长的词链}
ReWrite (Output);
输出词链
end else if List[l].L=MaxL then 输出词链;{找到另一个解}
Readln(Str){读入下一个单词}
end
end.
四、总结
本题除了用动态规划外,还可以通过建树的方法分析,即用一颗多叉树来存储所有的单词,并通过标记的方法找到最长链。根据输入文件已经按照字典顺序排序的条件,建树的过程和找最长链的过程都可以优化,而且多叉树也可以压缩为一个线性表。分析后的程序和动态规划的是一样的,只是对程序的解释不同。
分析中所用到的5条定理,都是从题目的条件入手,根据字典顺序和词链的定义得出的。表面上是对题目的分析,实际上是对动态规划所建立的有向无环图的特点的分析。而所有的优化,都是根据有向无环图(即题目的数学模型)的特殊性而得到的。
根据题目的条件,挖掘出有向无环图的特殊性质,从而优化算法。最终,时间复杂度降为n,空间复杂度降为常数。这说明,动态规划相对于搜索来说,的确对问题的数学模型有了更精确的刻画,但并不代表不能再优化。对于很多经典的动态规划模型,特别是用有向无环图的最长路径描述的问题,有时仍然十分粗糙。这时就必须进一步的挖掘出模型的特殊性质,优化动态规划方程,从而达到优化算法的目的。
前