【问题标题】:Find common approximate subtring with weight and penalty找到具有权重和惩罚的常见近似子串
【发布时间】:2021-12-27 04:30:32
【问题描述】:

给定两个只有英文字母的随机子串(不一定是相同的长度),使用动态规划找到最常见的近似子串。

每个字母都有一个给定值的权重w(即,将 A 设置为 1,B 设置为 2,或所有字母为 1),并以随机值惩罚 p

最佳“公共近似子串”定义为找到每个字母的最大和为w的最佳连续子串,并在出现不匹配时减去惩罚p

例如,给定两个字符串 "AABCC""AADCC",并带有不匹配惩罚 p=3。当“A”的 w 为 1 且“C”为 2 时,输出将是 "CC",因为它具有最高权重 2 + 2 = 4,超过了所有其他子字符串。如果每个字母的w 是1,那么输出将只是"AA"。等等

现在我可以理解找到最长公共子串https://www.geeksforgeeks.org/print-longest-common-substring/ 的动态方法,但我需要帮助想出一个动态编程算法,以便在 C++ 中找到最佳近似公共子串。我感觉这个问题只是为了找到有重量和惩罚的LCS。

【问题讨论】:

    标签: c++ string algorithm dynamic-programming


    【解决方案1】:

    编辑

    让我们为权重创建一个类。我不想为所有字母设置权重,因为我设置了default_weight

    class LetterWeights
    {
        std::map<char, double> weights;
        const double default_weight;
        const double penalty;
    public:
        LetterWeights(const double& def_weight, const double& pen)
            : default_weight(def_weight)
            , penalty(pen)
        {};
        void Set(const char& letter, const double& weight) { weights[letter] = weight; }
        const double Get(const char& letter)
        {
            if (weights.find(letter) != weights.end())
            {
                return weights[letter] - penalty;
            }
            else
            {
                return default_weight - penalty;
            }
        }
    };
    

    我认为我们需要自己的子字符串类,我们将有字符串的起始索引和子字符串的长度:

    static const int kEmptyIndex = -2;
    struct Substring
    {
        int start = kEmptyIndex, len = 0;
        double weight = 0;
        const bool Empty() const {return start == kEmptyIndex; }
        void Clear() 
        {
            start = kEmptyIndex; 
            len = 0;
            weight = 0;
        }
        std::string GetString(const std::string& s)
        {
            return s.substr(start, len);
        }
    };
    

    现在让我们编写 LRC。算法:
    输入:string1 (size = N), string2 (size = M)。
    结果:一对子字符串及其权重。 假设我们有一个表 X(N x M),如果 string1[i] == string2[j] 则 X(i, j) 等于 1,其他情况下为 0。所以所有子串都在 X 的对角线上:如果 X(0,1), x(1, 2) 和 X(1, 3) = 1 并且其他单元格 = 0 我们可以按长度找到最大子串 string1[0:1 ] 或 string2[1:2] 导致 X(0,1) 和 X(1, 2) 位于一条对角线上。所以我们可以按值填充表格并找到对角线的最大序列。
    我们可以填写表格并在O(M*N) 次中找到最大序列。
    但我不想创建表。没有它我们可以找到子串,但我们会“通过对角线”找到子串。我们将使用权重函数而不是长度(用于查找最大子字符串);
    让我们创建另一个有用的类:

    struct MaxSubstring
    {
        Substring max, curr;
        void CheckMax()
        {
            if (!curr.Empty() && (max.Empty() || curr.weight > max.weight))
                max = curr;
            curr.Clear();
        }
        void Add(const int index, const double& weight)
        {
            if (curr.Empty())
            {
                curr.start = index;
            }
            ++curr.len;
            curr.weight += weight;
        }
    };
    

    这里我们有关于 diag 的当前和最大子字符串。我需要用于清除复制粘贴的课程。现在我们可以编写主类了:

    class LCS
    {
        const std::string string1, string2;
    public:
        LCS(const std::string& s1, const std::string& s2)
            : string1(s1)
            , string2(s2)
        {};
        std::pair<std::string, double> FindMax(LetterWeights& weights)
        {
            MaxSubstring res;
            
            for (int i = 0; i < string1.size(); ++i)
            {
                res.curr = MaxDiagSum(weights, i, 0);
                res.CheckMax();
            }
            for (int j = 1; j < string2.size(); ++j)
            {
                res.curr = MaxDiagSum(weights, 0, j);
                res.CheckMax();
            }
            
            if (res.max.Empty())
            {
                return std::make_pair("", 0);
            }
            return std::make_pair(res.max.GetString(string1), res.max.weight);
        }
        Substring MaxDiagSum(LetterWeights& weights, const int i_start, const int j_start)
        {
            MaxSubstring res;
            int i1 = i_start, i2 = j_start;
            for ( ; i1 < string1.size() && i2 < string2.size(); ++i1, ++i2)
            {
                if (string1[i1] == string2[i2])
                {
                    res.Add(i1, weights.Get(string1[i1]));
                }
                else 
                {
                    res.CheckMax();
                }
            }
            res.CheckMax();
            return res.max;
        }
    };
    

    主函数举例:

    int main(void)
    {
        
        std::string s1, s2;
        std::cin >> s1;
        std::cin >> s2;
        double penalty = 1.0;
        LetterWeights weight(1.0, penalty);
        
        weight.Set('a', 10.0);
        
        LCS lcs(s1, s2);
        auto res = lcs.FindMax(weight);
        std::cout << res.first << " --> " << res.second << std::endl;
        
        return 0;
    }
    

    次:O(M*N);内存:O(max(M, N)).

    【讨论】:

    • 你好,我想你有点误解了,返回的子字符串需要是'contiguous',并且所有字母的惩罚值都是一样的。只有每个字母的重量不同。
    • 当我用两个字符串“aadcc”和“aabcc”测试它时,它返回“aacc”,它会跳过不匹配的字母,这不应该发生。
    • 你说得对,我的代码没用。但是我重写了。
    猜你喜欢
    • 1970-01-01
    • 2016-06-14
    • 2021-06-13
    • 1970-01-01
    • 1970-01-01
    • 2016-09-26
    • 2017-09-16
    • 2019-07-10
    • 2016-05-05
    相关资源
    最近更新 更多