最长公共子序列和最长公共子串——动态规划(C语言)

  本内容将介绍 最长公共子序列最长公共子串 的动态规划解法。

  两者之间的区别:最长公共子序列 不要求在原序列是连续的,而 最长公共子串 要求在原序列中是连续的。

一、最长公共子序列(longest-common-subsequence)

问题描述:
  给定两个序列 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...m1]Y[0...n1],长度分别为 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...m1],Y[0...n1]) 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...m1],Y[0...n1]) 的递归定义:

  1. 如果两个序列的最后一个元素匹配(即 X [ m − 1 ] = = Y [ n − 1 ] X[m-1] == Y[n-1] X[m1]==Y[n1]
    S ( X [ 0... m − 1 ] , Y [ 0... n − 1 ] ) = 1 + S ( X [ 0... m − 2 ] , Y [ 0... n − 2 ] ) S(X[0 ... m-1], Y[0 ... n-1]) = 1 + S(X[0 ... m-2], Y[0 ... n-2]) S(X[0...m1],Y[0...n1])=1+S(X[0...m2],Y[0...n2])
  2. 如果两个序列的最后一个元素不匹配(即 X [ m − 1 ] ! = Y [ n − 1 ] X[m-1] != Y[n-1] X[m1]!=Y[n1]
    S ( X [ 0... m − 1 ] , Y [ 0... n − 1 ] ) = m a x ( S ( X [ 0... m − 2 ] , Y [ 0... n − 1 ] ) , S ( X [ 0... m − 1 ] , Y [ 0... n − 2 ] ) ) S(X[0 ... m-1], Y[0 ... n-1]) = max(S(X[0 ... m-2], Y[0 ... n-1]), S(X[0 ... m-1], Y[0 ... n-2])) S(X[0...m1],Y[0...n1])=max(S(X[0...m2],Y[0...n1]),S(X[0...m1],Y[0...n2]))

代码实现:

#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;
}

二、最长公共子串(longest-common-subString)

问题描述:
  给定两个序列 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...m1] Y [ 0... n − 1 ] Y[0 ... n-1] Y[0...n1],长度分别为 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...m1],Y[0...n1]) 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...m1],Y[0...n1]) 的递归定义:

  1. 如果两个序列的最后一个元素匹配(即 X [ m − 1 ] = = Y [ n − 1 ] X[m-1] == Y[n-1] X[m1]==Y[n1]
    S ( X [ 0... m − 1 ] , Y [ 0... n − 1 ] ) = 1 + S ( X [ 0... m − 2 ] , Y [ 0... n − 2 ] ) S(X[0 ... m-1], Y[0 ... n-1]) = 1 + S(X[0 ... m-2], Y[0 ... n-2]) S(X[0...m1],Y[0...n1])=1+S(X[0...m2],Y[0...n2])
  2. 如果两个序列的最后一个元素不匹配(即 X [ m − 1 ] ! = Y [ n − 1 ] X[m-1] != Y[n-1] X[m1]!=Y[n1]
    需要将 S ( X [ 0... m − 1 ] , Y [ 0... n − 1 ] ) S(X[0 ... m-1], Y[0 ... n-1]) S(X[0...m1],Y[0...n1]) 清零,即 S ( X [ 0... m − 1 ] , Y [ 0... n − 1 ] ) = 0 S(X[0 ... m-1], Y[0 ... n-1]) = 0 S(X[0...m1],Y[0...n1])=0,注意这个地方与最长公共子序列不一样,因为最长公共子串要求连续。

代码实现:

#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;
}

你可能感兴趣的:(03_算法学习)