本内容将介绍 最长公共子序列 和 最长公共子串 的动态规划解法。
两者之间的区别:最长公共子序列 不要求在原序列是连续的,而 最长公共子串 要求在原序列中是连续的。
问题描述:
给定两个序列 X = x 1 , x 2 , ⋯   , x m X={x_1,x_2,\cdots,x_m} X=x1,x2,⋯,xm 和 Y = y 1 , y 2 ⋯   , y n Y={y_1,y_2\cdots,y_n} Y=y1,y2⋯,yn,求 X X X 和 Y Y Y 长度最长的公共子序列。
解题思路:
假设输入序列是 X [ 0... m − 1 ] 和 Y [ 0... n − 1 ] X[0 ... m-1]和Y[0 ... n-1] X[0...m−1]和Y[0...n−1],长度分别为 m m m 和 n n n。序列 S ( X [ 0... m − 1 ] , Y [ 0... n − 1 ] ) S(X[0 ... m-1], Y[0 ... n-1]) S(X[0...m−1],Y[0...n−1]) 是 X X X 和 Y Y Y 两个序列的 L C S LCS LCS 的长度。
以下为 S ( X [ 0... m − 1 ] , Y [ 0... n − 1 ] ) S(X[0 ... m-1], Y[0 ... n-1]) S(X[0...m−1],Y[0...n−1]) 的递归定义:
代码实现:
#include
#define LENGTH (101)
char Str1[LENGTH]; // 存放字符串数组1
char Str2[LENGTH]; // 存放字符串数组2
// Size[i][j]表示Str1[0 - (i-1)]和Str2[0 - (j-1)]的最长公共子序列的长度
// Size[0][j]表示Str1为null时,Str1和Str2的最长公共子序列的长度,它为0
int Size[LENGTH][LENGTH];
// Trace[i][j]记录Str1[i]和Str2[j]的状态
int Trace[LENGTH][LENGTH];
// 获取字符串数组的长度
int getStrLength(char str[]) {
int i;
for(i = 0; i < LENGTH; i++) {
if(str[i] == '\0') {
break;
}
}
return i;
}
// 打印出最长公共子序列
void print_lcs(int length1, int length2) {
if(length1 == 0 || length2 == 0) {
return;
}
if(Trace[length1][length2] == 1) {
// 表示当前最后的一个字符相同,需要打印出来
print_lcs(length1-1, length2-1);
printf("%c", Str1[length1 - 1]);
} else if(Trace[length1][length2] == 2) {
print_lcs(length1-1, length2);
} else {
print_lcs(length1, length2-1);
}
}
void LongestSubSequence(char str1[], char str2[]) {
int i, j;
int str1_len, str2_len; // 保存字符串数组的长度
str1_len = getStrLength(str1);
str2_len = getStrLength(str2);
// Str1或者Str2中不存在字符时,不存在公共子序列,直接返回
if(str1_len == 0 || str2_len == 0) {
printf("%s\n", "No sub sequence");
return;
}
// 当Str2中没有字符时,最长公共子串长度为0
for(i = 0; i <= str1_len; i++) {
Size[i][0] = 0;
}
// 当Str1中没有字符时,最长公共子串长度为0
for(i = 0; i <= str2_len; i++) {
Size[0][i] = 0;
}
for(i = 1; i <= str1_len; i++) {
for(j = 1; j <= str2_len; j++) {
// 注意:下面是使用i-1和j-1
// 因为Size中的i和j表示数组中字符个数,0表示没有;而Str1[0]和Str2[0]表示第一个字符
if(str1[i-1] == str2[j-1]) {
// 当两个数组的最后一个字符相同时
Size[i][j] = Size[i-1][j-1] + 1;
Trace[i][j] = 1;
} else if(Size[i-1][j] > Size[i][j-1]) {
Size[i][j] = Size[i-1][j];
Trace[i][j] = 2;
} else {
Size[i][j] = Size[i][j-1];
Trace[i][j] = 3;
}
}
}
if(Size[str1_len][str2_len] <= 0) {
printf("%s\n", "No sub sequence");
return;
}
printf("size = %d\n", Size[str1_len][str2_len]);
print_lcs(str1_len, str2_len);
printf("\n");
}
int main() {
int i;
int count;
freopen("input.txt", "r", stdin);
scanf("%d", &count);
for(i = 0; i < count; i++) {
scanf("%s", Str1);
scanf("%s", Str2);
LongestSubSequence(Str1, Str2);
}
return 0;
}
问题描述:
给定两个序列 X = x 1 , x 2 , ⋯   , x m X={x_1,x_2,\cdots,x_m} X=x1,x2,⋯,xm 和 Y = y 1 , y 2 , ⋯   , y n Y={y_1,y_2,\cdots,y_n} Y=y1,y2,⋯,yn,求 X X X 和 Y Y Y 长度最长的公共子串。
解题思路:
假设输入序列是 X [ 0... m − 1 ] X[0 ... m-1] X[0...m−1] 和 Y [ 0... n − 1 ] Y[0 ... n-1] Y[0...n−1],长度分别为 m m m 和 n n n。序列 S ( X [ 0... m − 1 ] , Y [ 0... n − 1 ] ) S(X[0 ... m-1], Y[0 ... n-1]) S(X[0...m−1],Y[0...n−1]) 是 X X X 和 Y Y Y 两个序列的最长的公共子串的长度。
以下为 S ( X [ 0... m − 1 ] , Y [ 0... n − 1 ] ) S(X[0 ... m-1], Y[0 ... n-1]) S(X[0...m−1],Y[0...n−1]) 的递归定义:
代码实现:
#include
#define LENGTH (101)
char Str1[LENGTH]; // 存放字符串数组1
char Str2[LENGTH]; // 存放字符串数组2
// Size[i][j]表示Str1[0 - (i-1)]和Str2[0 - (j-1)]的最长公共子串的长度
// Size[0][j]表示Str1为null时,Str1和Str2的最长公共子串的长度,它为0
int Size[LENGTH][LENGTH];
// 获取字符串数组的长度
int getStrLength(char str[]) {
int i;
for(i = 0; i < LENGTH; i++) {
if(str[i] == '\0') {
break;
}
}
return i;
}
void LongestSubString(char str1[], char str2[]) {
int i, j;
int str1_len, str2_len; // 保存字符串数组的长度
int max_sub_len = 0; // 保存最长公共子串的长度
int lastSubEnd = 0; // 最长公共子串的最后一个字符在Str2数组的下标
str1_len = getStrLength(str1);
str2_len = getStrLength(str2);
// Str1或者Str2中不存在字符时,不存在公共子串,直接返回
if(str1_len == 0 || str2_len == 0) {
printf("%s\n", "No sub string");
return;
}
// 当Str2中没有字符时,最长公共子串长度为0
for(i = 0; i <= str1_len; i++) {
Size[i][0] = 0;
}
// 当Str1中没有字符时,最长公共子串长度为0
for(i = 0; i <= str2_len; i++) {
Size[0][i] = 0;
}
for(i = 1; i <= str1_len; i++) {
for(j = 1; j <= str2_len; j++) {
// 注意:下面是使用i-1和j-1
// 因为Size中的i和j表示数组中字符个数,0表示没有;而Str1[0]和Str2[0]表示的是第一个字符
if(str1[i-1] == str2[j-1]) {
// 当两个数组的最后一个字符相同时
Size[i][j] = 1 + Size[i - 1][j - 1];
} else {
// 当两个数组的最后一个字符相同,说明公共子串不会继续增加,所以赋值0
Size[i][j] = 0;
}
if (Size[i][j] > max_sub_len) {
max_sub_len = Size[i][j];
lastSubEnd = j-1;
}
}
}
// 根据max_sub_len(最长公共子串的长度)和lastSubEnd(最长公共子串的最后一个字符在Str2数组的下标),打印出最长公共子串
for(i = lastSubEnd - max_sub_len + 1; i <= lastSubEnd && i >= 0; i++) {
printf("%c", str2[i]);
}
printf("\n");
}
int main() {
int i;
int count;
freopen("input.txt", "r", stdin);
scanf("%d", &count);
for(i = 0; i < count; i++) {
scanf("%s", Str1);
scanf("%s", Str2);
LongestSubString(Str1, Str2);
}
return 0;
}