LCS 问题求解

2021-10-28  本文已影响0人  奇点创客

LCS : longest common substring 即最长公共子串

LCS 问题就是求两个字符串最长公共子串的问题。
解法就是用一个矩阵来记录两个字符串中所有位置的两个字符之间的匹配情况,若是匹配则为1,否则为0。然后求出对角线最长的1序列,其对应的位置就是最长匹配子串的位置。

但是在0和1的矩阵中找最长的1对角线序列又要花去一定的时间。通过改进矩阵的生成方式和设置标记变量,可以省去这部分时间。

当字符匹配的时候,我们并不是简单的给相应元素赋上1,而是赋上其左上角元素的值加一。我们用两个标记变量来标记矩阵中值最大的元素的位置,在矩阵生成的过程中来判断当前生成的元素的值是不是最大的,据此来改变标记变量的值,那么到矩阵完成的时候,最长匹配子串的位置和长度就已经出来了。

算法的基本思想:
当字符匹配的时候,不是简单的给相应元素赋上1,而是赋上其左上角元素的值加一。
我们用两个标记变量来标记矩阵中值最大的元素的位置,在矩阵生成的过程中来判断
当前生成的元素的值是不是最大的,据此来改变标记变量的值,那么到矩阵完成的时
候,最长匹配子串的位置和长度就已经出来了。

C++ 实现

// std::format  为 C++20 特性
#include "stdc++.h"

using Table = std::vector<std::vector<int>>;

void print_table(const string& s1, const string& s2, const Table& t)
{
    cout << "  ";
    std::for_each(s1.cbegin(), s1.cend(), [](char ch){cout << format("{:>3}", ch);});
    cout << "\n";
    for (size_t i = 0; i < s2.size(); i++) {
        cout << format("{:>2}", s2[i]);
        for (size_t j = 0; j < s1.size(); j++)
            cout << format("{:>3}", t[i][j]);
        cout << "\n";
    }
}

string LCS(const string& s1, const string& s2)
{
    size_t s1_len = s1.size(), s2_len = s2.size();
    Table t(s2_len);
    std::for_each(t.begin(), t.end(), [s1_len](auto& v){ v.resize(s1_len); });

    size_t lcs_begin{}, lcs_end{}, lcs_len{};
    for (size_t i = 0; i < s2_len; i++) {
        for (size_t j =0; j < s1_len; j++) {
            if (s1[j] == s2[i]) t[i][j] = (i == 0 or j == 0) ? 1 :  t[i - 1][j - 1] + 1;  
            else t[i][j] = 0;
            if (lcs_len < t[i][j]) { lcs_len = t[i][j]; lcs_end = j; }
        }
    }
    print_table(s1, s2, t);
    lcs_begin = lcs_end - lcs_len + 1;

    return s1.substr(lcs_begin, lcs_len);
}

int main()
{
    string s1{"hello,world,123"};
    string s2{"HHHello,world,12345"};
    string lcs = LCS(s1, s2);
    cout << format("字符串: {} 和\n字符串: {} 的\n最长公共子串是:{}", s1, s2, lcs);
}

Python 实现:

# LCS : longest common substring 即最长公共子串

def print_table(s1, s2, t):
    print("-"*20)
    print("  ", end='')
    for i in range(len(s1)):
        print("{:>3}".format(s1[i]), end = '')
    print("")

    for i in range(len(s2)):
        print("{:>2}".format(s2[i]), end = '')
        for j in range(len(s1)):
            print("{:>3}".format(t[i][j]), end = '')
        print("")
 
def LCS(s1, s2):
    print("寻找 {} 和 {} 的最大公共子串".format(s1, s2))
    s1_len, s2_len = len(s1), len(s2)
    t = [[0 for _ in range(s1_len)] for _ in range(s2_len)]
    lcs_begin, lcs_end, lcs_len = 0, 0, 0

    for i in range(s2_len):
        for j in range(s1_len):
            if s1[j] == s2[i]:
                if i == 0 or j == 0: t[i][j] = 1
                else:                t[i][j] = t[i - 1][j - 1] + 1
            else:                    t[i][j] = 0

            if t[i][j] > lcs_len:
                lcs_len = t[i][j]; lcs_end = j

    print_table(s1, s2, t)
    lcs_begin = lcs_end - lcs_len + 1

    return s1[lcs_begin : lcs_end + 1]

if __name__ == '__main__':
    strs = ["hello", "hell", "Hello", "llo", "hello, world"]
    print("字符串列表:", strs)
    lcs = strs[0]
    for s in strs[1:]:
        lcs = LCS(lcs, s)
    
    print("字符串列表的最长公共子串是:{}".format(lcs))                         
上一篇下一篇

猜你喜欢

热点阅读