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

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

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

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

问题描述:

  给定两个序列 X = x 1 , x 2 , , x m Y = y 1 , y 2 , y n ,求 X Y 长度最长的公共子序列。

解题思路:

  假设输入序列是 X [ 0 . . . m 1 ] Y [ 0 . . . n 1 ] ,长度分别为 m n 。序列 S ( X [ 0 . . . m 1 ] , Y [ 0 . . . n 1 ] ) X Y 两个序列的 L C S 的长度。
  以下为 S ( X [ 0 . . . m 1 ] , Y [ 0 . . . n 1 ] ) 的递归定义:

  1. 如果两个序列的最后一个元素匹配(即 X [ m 1 ] == Y [ n 1 ]
    S ( X [ 0 . . . m 1 ] , Y [ 0 . . . n 1 ] ) = 1 + S ( X [ 0 . . . m 2 ] , Y [ 0 . . . n 2 ] )
  2. 如果两个序列的最后一个元素不匹配(即 X [ m 1 ] ! = Y [ n 1 ]
    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 ] ) )

代码实现:

#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)

问题描述:

  给定两个序列 X = x 1 , x 2 , , x m Y = y 1 , y 2 , , y n ,求 X Y 长度最长的公共子串。

解题思路:

  假设输入序列是 X [ 0 . . . m 1 ] Y [ 0 . . . n 1 ] ,长度分别为 m n 。序列 S ( X [ 0 . . . m 1 ] , Y [ 0 . . . n 1 ] ) X Y 两个序列的最长的公共子串的长度。
  以下为 S ( X [ 0 . . . m 1 ] , Y [ 0 . . . n 1 ] ) 的递归定义:

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

代码实现:

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

猜你喜欢

转载自blog.csdn.net/benzhujie1245com/article/details/82664278