【问题标题】:LCS algorithm: How to find out from a Table, how many longest common subsequences are found?LCS算法:如何从一个表中找出,找到了多少个最长公共子序列?
【发布时间】:2019-06-09 22:33:35
【问题描述】:

我在 C# 中实现了最长公共子序列问题。我需要检测两个字符串之间的所有常见的最大子序列

为此,我使用Needleman-Wunsch algorithm 创建了一个表来存储每个计算步骤的 LCS 序列。

是否有机会确定,找到了多少个最大子序列(使用表格)?

基于此,我想选择一种方法来收集每个子序列。关键是,对于一个子序列,不需要递归,因此它会提供更好的性能。这对我的任务至关重要。

这是一个代码 sn-p,其中实现了项目的基本功能:

    private static int[][] GetMatrixLCS(string x, string y)
        {
            var lenX = x.Length;
            var lenY = y.Length;
            matrixLCS = new int[lenX + 1][];
            for (var i = 0; i < matrixLCS.Length; i++)
            {
                matrixLCS[i] = new int[lenY + 1];
            }
            for (int i = 0; i <= lenX; i++)
            {
                for (int j = 0; j <= lenY; j++)
                {
                    if (i == 0 || j == 0)
                        matrixLCS[i][j] = 0;
                    else
                    if (x[i - 1] == y[j - 1])
                        matrixLCS[i][j] = matrixLCS[i - 1][j - 1] + 1;
                    else
                        matrixLCS[i][j] = Math.Max(matrixLCS[i - 1][j], matrixLCS[i][j - 1]);
                }
            }
            return matrixLCS;
        }

    static HashSet<string> FindAllLcs(string X, string Y, int lenX, int lenY)
        {
            var set = new HashSet<string>();
            if (lenX == 0 || lenY == 0)
                return emptySet;
            if (X[lenX - 1] == Y[lenY - 1])
            {
                var tempResult = FindAllLcs(X, Y, lenX - 1, lenY - 1);
                foreach (var temp in tempResult)
                    set.Add(temp + X[lenX - 1]);
                return set;
            }
            if (matrixLCS[lenX - 1][lenY] >= matrixLCS[lenX][lenY - 1])
                set = FindAllLcs(X, Y, lenX - 1, lenY);
            if (matrixLCS[lenX][lenY - 1] >= matrixLCS[lenX - 1][lenY])
                set.UnionWith(FindAllLcs(X, Y, lenX, lenY - 1));
            return set;
        }

以及具有两种输入和预期输出的示例:

    public void SingleOutput()
    {
    var sequence = LCS.FindLCS("ABC", "AB");
    Assert.AreEqual(1, sequence.Length);
    Assert.AreEqual("AB", sequence[0]);
    }

    public void MultipleOutput() 
    { 
    var sequence = LCS.FindLCS("BCAB", "ABC"); 
    Assert.AreEqual(2, sequence.Length); 
    Assert.AreEqual("AB", sequence [0]);
    Assert.AreEqual("BC", sequence [1]);
    }

我们将不胜感激。

【问题讨论】:

标签: c# algorithm lcs needleman-wunsch


【解决方案1】:

我认为可以稍微不同地考虑动态编程。也许它可以工作:

#include <bits/stdc++.h>

using namespace std;


struct TLSTValue {
    int len;
    int cnt;
};


void update(TLSTValue& v, const TLSTValue& u) {
    if (u.cnt == 0) {
        return;
    }
    if (u.len > v.len) {
        v.len = u.len;
        v.cnt = u.cnt;
    } else if (u.len == v.len) {
        v.cnt += u.cnt;
    }
}

int main(int /* argc */, char** /* argv */)
{
    string a, b;
    while (cin >> a >> b) {
        int n = a.size();
        int m = b.size();

        vector<vector<int>> nxt(n, vector<int>(m));
        for (int j = 0; j < m; ++j) {
            int lst = n;
            for (int i = n - 1; i >= 0; --i) {
                if (a[i] == b[j]) {
                    lst = i;
                }
                nxt[i][j] = lst;
            }
        }

        vector<vector<TLSTValue>> f(n + 1, vector<TLSTValue>(m + 1, {0, 0}));
        f[0][0]= {0, 1};
        TLSTValue ans = {0, 0};
        for (int i = 0; i <= n; ++i) {
            unordered_set<char> st;
            for (int j = 0; j <= m; ++j) {
                update(ans, f[i][j]);
                if (j) {
                    update(f[i][j], f[i][j - 1]);
                }
                if (st.count(b[j])) {
                    continue;
                }
                st.insert(b[j]);
                if (i < n && j < m && f[i][j].cnt && nxt[i][j] < n) {
                    update(f[nxt[i][j] + 1][j + 1], {f[i][j].len + 1, f[i][j].cnt});
                }
            }
        }
        cout << a << " and " << b << ": length = " << ans.len << ", count = " << ans.cnt << endl;
    }

    cerr << "Time execute: " << clock() / (double)CLOCKS_PER_SEC << " sec" << endl;
    return 0;
}

nxt[i][j] 是字符串a 中从i 位置开始的第一个位置@ 字符串b 中位置为j 的字符。 f[i][j] 是长度和计数 LCS,它以字符串 a 中的字符 i - 1 和字符串 b 中的位置 j 之前结束。

你可以试试代码here

一些测试的输出:

ABC and AB: length = 2, count = 1
BCAB and ABC: length = 2, count = 2
A and AAA: length = 1, count = 1
AAA and A: length = 1, count = 1
AAAB and ABBB: length = 2, count = 1
ABBB and AAAB: length = 2, count = 1

【讨论】:

    【解决方案2】:

    最简单的方法是使用朴素实现通过矩阵记录迭代期间的所有匹配项。

    我需要allLCS() 进行测试,如果其他算法提供有效的解决方案,它必须是所有可能的 LCS 之一。

    代码在github

    它在 Perl 中,但很容易理解。遍历矩阵并在单元格中添加匹配项。最后右下角的单元格包含 LCS 的长度。这就是天真的算法。现在在每次匹配时将坐标记录为哈希中的数组 [i,j],匹配计数作为键。

    # get all LCS of two arrays
    # records the matches by rank
    sub allLCS {
      my ($self,$X,$Y) = @_;
    
      my $m = scalar @$X;
      my $n = scalar @$Y;
    
      my $ranks = {}; # e.g. '4' => [[3,6],[4,5]]
      my $c = [];
      my ($i,$j);
    
      for (0..$m) {$c->[$_][0]=0;}
      for (0..$n) {$c->[0][$_]=0;}
      for ($i=1;$i<=$m;$i++) {
        for ($j=1;$j<=$n;$j++) {
          if ($X->[$i-1] eq $Y->[$j-1]) {
            $c->[$i][$j] = $c->[$i-1][$j-1]+1;
            push @{$ranks->{$c->[$i][$j]}},[$i-1,$j-1];
          }
          else {
            $c->[$i][$j] =
              ($c->[$i][$j-1] > $c->[$i-1][$j])
                ? $c->[$i][$j-1]
                : $c->[$i-1][$j];
          }
        }
      }
      my $max = scalar keys %$ranks;
      return $self->_all_lcs($ranks,1,$max);
    } 
    
    

    最后,这个记录的匹配集合通过_all_lcs()方法连接起来:

    sub _all_lcs {
      my ($self,$ranks,$rank,$max) = @_;
    
      my $R = [[]];
    
      while ($rank <= $max) {
        my @temp;
        for my $path (@$R) {
          for my $hunk (@{$ranks->{$rank}}) {
            if (scalar @{$path} == 0) {
              push @temp,[$hunk];
            }
            elsif (($path->[-1][0] < $hunk->[0]) && ($path->[-1][1] < $hunk->[1])) {
              push @temp,[@$path,$hunk];
            }
          }
        }
        @$R = @temp;
        $rank++;
      }
      return $R;
    }
    

    代码灵感来源于论文

    罗纳德·格林伯格。 Fast and Simple Computation of All Longest Common Subsequences

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-12-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-10
      • 2021-12-14
      相关资源
      最近更新 更多