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

问题描述


我们称序列 
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

输出样例 





解题思路


如果我们用字符数组 
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. } 


常见问题


求解最长公共子序列时,当比较到两个字符串的两个字母不同时,应该分别将两个字
符串向后移动一个字符,比较这两种情况中哪个得到的公共子序列最长。有些同学只将其中
的一个字符串向后移动,或者两个同时移动,都是不对的。 

你可能感兴趣的:(c,测试,zk,存储,任务)