问题:需要两个字符串之间的 LCS 长度。字符串的大小最多为 100 个字符。字母表是通常的 DNA 之一,4 个字符“ACGT”。动态方法不够快。
我的问题是我正在处理成对的成对(据我所见,数量为数亿)。我相信我已将 LCS_length 函数的调用减少到尽可能少的程度,因此使我的程序运行得更快的唯一其他方法是拥有更高效的 LCS_Length 函数。
我已经开始使用通常的动态编程方法来实现。
这给出了正确的答案,并有望得到正确实现。
#define arrayLengthMacro(a) strlen(a) + 1
#define MAX_STRING 101
static int MaxLength(int lengthA, int lengthB);
/*
* Then the two strings are compared following a dynamic computing
* LCS table algorithm. Since we only require the length of the LCS
* we can get this rather easily.
*/
int LCS_Length(char *a, char *b)
{
int lengthA = arrayLengthMacro(a),lengthB = arrayLengthMacro(b),
LCS = 0, i, j, maxLength, board[MAX_STRING][MAX_STRING];
maxLength = MaxLength(lengthA, lengthB);
//printf("%d %d\n", lengthA, lengthB);
for (i = 0; i < maxLength - 1; i++)
{
board[i][0] = 0;
board[0][i] = 0;
}
for (i = 1; i < lengthA; i++)
{
for (j = 1; j < lengthB; j++)
{
/* If a match is found we allocate the number in (i-1, j-1) incremented
* by 1 to the (i, j) position
*/
if (a[i - 1] == b[j - 1])
{
board[i][j] = board[i-1][j-1] + 1;
if(LCS < board[i][j])
{
LCS++;
}
}
else
{
if (board[i-1][j] > board[i][j-1])
{
board[i][j] = board[i-1][j];
}
else
{
board[i][j] = board[i][j-1];
}
}
}
}
return LCS;
}
那应该是 O(mn)(希望如此)。
然后为了寻找速度,我找到了这篇文章 Longest Common Subsequence
这给了迈尔斯的 O(ND) paper。我尝试了这个,它将 LCS 与最短的编辑脚本 (SES) 联系起来。
他们给出的关系是 D = M + N - 2L,其中 D 是 SES 的长度,M 和 N 是两个字符串的长度,L 是 LCS 长度。但这在我的实现中并不总是正确的。我给出了我的实现(我认为是正确的):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define arrayLengthMacro(a) strlen(a)
int LCS_Length (char *a, char *b);
int MinLength (int A, int B);
int Max (int A, int B);
int snake(int k, int max, char *a, char *b, int lengthA, int lengthB);
int main(void)
{
int L;
char a[] = "tomato", b[] = "potato"; //must give LCS = 4
L = LCS_Length(a, b);
printf("LCS: %d\n", L );
char c[] = "GTCGTTCGGAATGCCGTTGCTCTGTAAA", d[] = "ACCGGTCGAGTGCGCGGAAGCCGGCCGAA"; // must give LCS = 20
L = LCS_Length(c, d);
printf("LCS: %d\n", L );
char e[] = "human", f[] = "chimpanzee"; // must give LCS = 4
L = LCS_Length(e, f);
printf("LCS: %d\n", L );
char g[] = "howareyou", h[] = "whoareyou"; // LCS =8
L = LCS_Length(g, h);
printf("LCS: %d\n", L );
char i[] = "TTCTTTCGGTAACGCCTACTTTATGAAGAGGTTACATTGCAATCGGGTAAATTAACCAACAAGTAATGGTAGTTCCTAGTAGAGAAACCCTCCCGCTCAC",
j[] = "GCACGCGCCTGTTGCTACGCTCTGTCCATCCTTTGTGTGCCGGGTACTCAGACCGGTAACTCGAGTTGCTATCGCGGTTATCAGGATCATTTACGGACTC"; // 61
L = LCS_Length(i, j);
printf("LCS: %d\n", L );
return 0;
}
int LCS_Length(char *a, char *b)
{
int D, lengthA = arrayLengthMacro(a), lengthB = arrayLengthMacro(b),
max, *V_, *V, i, k, x, y;
max = lengthA + lengthB;
V_ = malloc(sizeof(int) * (max+1));
if(V_ == NULL)
{
fprintf(stderr, "Failed to allocate memory for LCS");
exit(1);
}
V = V_ + lengthA;
V[1] = 0;
for (D = 0; D < max; D++)
{
for (k = -D; k <= D; k = k + 2)
{
if ((k == -D && V[k-1] < V[k+1]) || (k != D && V[k-1] < V[k+1]))
{
x = V[k+1];
}
else
{
x = V[k-1] + 1;
}
y = x - k;
while ((x < lengthB) && (y < lengthA) && (a[x+1] == b[y+1]))
{
x++;
y++;
}
V[k] = x;
if ((x >= lengthB) && (y >= lengthA))
{
return (lengthA + lengthB - D)/2;
}
}
}
return (lengthA + lengthB - D)/2;
}
主要有例子。
例如。 “tomato”和“potato”(不评论),LCS 长度为 4。
实现发现它是 5 sice SES(代码中的 D)作为 2 而不是我期望的 4(删除“t”,插入“p”,删除“m”,插入“t”)。我倾向于认为 O(ND) 算法可能也会计算替换,但我不确定这一点。
欢迎任何可实现的方法(我没有丰富的编程技能)。
(例如,如果有人知道如何利用小字母表)。
编辑:我认为在其他所有事情之上说我在任何时间在任何两个字符串之间使用 LCS 函数会很有用。所以它不仅仅是string say s1,与其他几百万相比。它可能是 s200 和 s1000,然后是 s0 和 s10000,然后是 250 和 s100000。也不太可能跟踪最常用的字符串。
我要求 LCS 长度不是近似值,因为我正在实现近似算法并且我不想添加额外的错误。
编辑:刚刚运行 callgrind。对于 10000 个字符串的输入,对于不同的字符串对,我似乎调用了 lcs 函数大约 50,000,000 次。 (10000 个字符串是我想要运行的最低字符串,最大值是 100 万(如果可行))。
最佳答案
有几种方法可以加快计算速度:
编辑:对于比较到同一组刺痛的情况,有人建议使用 BK-Tree 数据结构
Efficient way of calculating likeness scores of strings when sample size is large?
关于algorithm - 最长公共(public)子序列 (LCS) 长度的 Fast(er) 算法,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/6555873/