您的位置:首页 > 其它

10.5例题:动态规划典型题--最长公共子序列

2011-11-10 09:58 232 查看
问题描述

我们称序列

Z = < z1, z2, ..., zk >是序列

X = < x1, x2, ..., xm >的子序列当且仅当存在严格上

升的序列< i1, i2, ..., ik >,使得对

j = 1, 2, ... ,k, 有

xij = zj。比如

Z = < a, b, f, c >是

X = < a, b,

c, f, b, c >的子序列。

现在给出两个序列

X和

Y,你的任务是找到

X和

Y的最大公共子序列,也就是说要找

到一个最长的序列

Z,使得

Z既是

X的子序列也是

Y的子序列。

输入数据

输入包括多组测试数据。每组数据包括一行,给出两个长度不超过

200的字符串,表示

两个序列。两个字符串之间由若干个空格隔开。

输出要求

对每组输入数据,输出一行,给出两个序列的最大公共子序列的长度。

输入样例

abcfbc abfcab

programming contest

abcd mnp

输出样例

4

2

0

解题思路

如果我们用字符数组

s1、s2存放两个字符串,用

s1[i]表示

s1中的第

i个字符,

s2[j]表



s2中的第

j个字符(字符编号从

1开始,不存在“第

0个字符”),用

s1i表示

s1的前

i

个字符所构成的子串, s2j表示

s2的前

j个字符构成的子串,

MaxLen(i, j)表示

s1i和

s2j的

最长公共子序列的长度,那么递推关系如下:

if( i ==0 || j == 0 ) {

MaxLen(i, j) = 0 //两个空串的最长公共子序列长度当然是 0

}

else if( s1[i] == s2[j] )

MaxLen(i, j) = MaxLen(i-1, j-1 ) + 1;

else {

MaxLen(i, j) = Max( MaxLen(i, j-1), MaxLen(i-1, j));

}

MaxLen(i, j) = Max( MaxLen(i, j-1), MaxLen(i-1, j)) 这个递推关系需要证明一下。我们用

反证法来证明,MaxLen(i, j)不可能比

MaxLen(i, j-1)和

MaxLen(i-1, j)都大。先假设

MaxLen(i,

j)比

MaxLen(i-1, j)大。如果是这样的话,那么一定是

s1[i]起作用了,即

s1[i]是

s1i和

s2j的

最长公共子序列里的最后一个字符。同样,如果

MaxLen(i, j)比

MaxLen(i, j-1)大,也能够推

导出,s2[j]是

s1i和

s2j的最长公共子序列里的最后一个字符。即,如果

MaxLen(i, j)比

MaxLen(i, j-1)和

MaxLen(i-1, j)都大,那么,

s1[i]应该和

s2[j]相等。但这是和应用本递推关系

的前提----- s1[i]≠s2[j]相矛盾的。因此,

MaxLen(i, j)不可能比

MaxLen(i, j-1)和

MaxLen(i-1, j)

都大。MaxLen(i, j)当然不会比

MaxLen(i, j-1)和

MaxLen(i-1, j)中的任何一个小,因此,

MaxLen(i, j) = Max( MaxLen(i, j-1), MaxLen(i-1, j)) 必然成立。

显然本题目的“状态”就是

s1中的位置

i和

s2中的位置

j。“值”就是

MaxLen(i, j)。状

态的数目是

s1长度和

s2长度的乘积。可以用一个二维数组来存储各个状态下的“值”。本

问题的两个子问题,和原问题形式完全一致的,只不过规模小了一点。

参考程序:

1. #include <stdio.h> 
2. #include <string.h> 
3. #define MAX_LEN 1000 
4. char sz1[MAX_LEN]; 
5. char sz2[MAX_LEN]; 
6. int aMaxLen[MAX_LEN][MAX_LEN]; 
7. main() 
8. { 
9. while( scanf("%s%s", sz1+1 ,sz2+1 ) > 0 ) { 
10. int nLength1 = strlen( sz1+1); 
11. int nLength2 = strlen( sz2+1); 
12. int nTmp; 
13. int i, j; 
14. for( i = 0;i <= nLength1; i ++ ) 
15. aMaxLen[i][0] = 0; 
16. for( j = 0;j <= nLength2; j ++ ) 
17. aMaxLen[0][j] = 0; 
18. for( i = 1;i <= nLength1;i ++ ) { 
19. for( j = 1; j <= nLength2; j ++ ) { 
20. if( sz1[i] == sz2[j] ) 
21. aMaxLen[i][j] = 
22. aMaxLen[i-1][j-1] + 1; 
23. else { 
24. int nLen1 = aMaxLen[i][j-1]; 
25. int nLen2 = aMaxLen[i-1][j]; 
26. if( nLen1 > nLen2 ) 
27. aMaxLen[i][j] = nLen1; 
28. else 
29. aMaxLen[i][j] = nLen2; 
30. } 
31. } 
32. } 
33. printf("%d\n", aMaxLen[nLength1][nLength2]); 
34. } 
35. }


常见问题

求解最长公共子序列时,当比较到两个字符串的两个字母不同时,应该分别将两个字

符串向后移动一个字符,比较这两种情况中哪个得到的公共子序列最长。有些同学只将其中

的一个字符串向后移动,或者两个同时移动,都是不对的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: