本内容将介绍最长公共子序列和最长公共子串的动态规划解法。
两者之间的区别:最长公共子序列不要求在原序列是连续的,而最长公共子串要求在原序列中是连续的。
一、最长公共子序列(longest-common-subsequence)
问题描述:
给定两个序列
和
,求
和
长度最长的公共子序列。
解题思路:
假设输入序列是
,长度分别为
和
。序列
是
和
两个序列的
的长度。
以下为
的递归定义:
- 如果两个序列的最后一个元素匹配(即
)
- 如果两个序列的最后一个元素不匹配(即
)
代码实现:
#include <stdio.h>
#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;
}
二、最长公共子串(longest-common-subString)
问题描述:
给定两个序列
和
,求
和
长度最长的公共子串。
解题思路:
假设输入序列是
和
,长度分别为
和
。序列
是
和
两个序列的最长的公共子串的长度。
以下为
的递归定义:
- 如果两个序列的最后一个元素匹配(即
)
- 如果两个序列的最后一个元素不匹配(即
)
需要将 清零,即 ,注意这个地方与最长公共子序列不一样,因为最长公共子串要求连续。
代码实现:
#include <stdio.h>
#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;
}