C 什么';这个算法的运行时间是多少?
我刚刚写了一个问题的答案: 这个函数应该在两个字符串之间找到最长的子字符串,但是当我试图找出最坏情况下的运行时以及导致这种情况的输入时,我意识到我不知道。考虑代码是C伪代码。C 什么';这个算法的运行时间是多少?,c,algorithm,complexity-theory,C,Algorithm,Complexity Theory,我刚刚写了一个问题的答案: 这个函数应该在两个字符串之间找到最长的子字符串,但是当我试图找出最坏情况下的运行时以及导致这种情况的输入时,我意识到我不知道。考虑代码是C伪代码。 // assume the shorter string is passed in as A int lcs(char * A, char * B) { int length_a = strlen(A); int length_b = strlen(B); // This holds the length
// assume the shorter string is passed in as A
int lcs(char * A, char * B)
{
int length_a = strlen(A);
int length_b = strlen(B);
// This holds the length of the longest common substring found so far
int longest_length_found = 0;
// for each character in one string (doesn't matter which), look for
// incrementally larger strings in the other
// once a longer substring can no longer be found, stop
for (int a_index = 0; a_index < length_a - longest_length_found; a_index++) {
for (int b_index = 0; b_index < length_b - longest_length_found; b_index++) {
// check the next letter until a mismatch is found or one of the strings ends.
for (int offset = 0;
A[a_index+offset] != '\0' &&
B[b_index+offset] != '\0' &&
A[a_index+offset] == B[b_index+offset];
offset++) {
longest_length_found = longest_length_found > offset ? longest_length_found : offset;
}
}
}
return longest_found_length;
}
我是接近了还是错过了什么或误用了什么
我很确定我可以使用trie/前缀树做得更好,但我还是很想了解这段特定代码的行为。我认为roliu在评论中说的话非常划算。我认为你的算法是O(N3),最好的情况是O(N2)
我实际上想指出的是这个算法的过度放纵。您可以看到,对于每个字符串中的每个可能的起始偏移量,您将测试每个后续匹配字符以计算匹配数。但是考虑这样的事情:
A = "01111111"
B = "11111110"
几乎你会发现的第一件事是从A[1]
和B[0]
开始的最大匹配子字符串,然后你会测试部分精确的重叠,从A[2]
开始,B[1]
等等。。。这里重要的是相对偏移量。通过实现这一点,您可以完全删除算法的N3部分。然后就变成了将一个数组移到另一个数组之下的问题
A 01111111
B 11111110
B 11111110
B 11111110
B ... -->
B 11111110
为了降低代码的复杂性,您可以只测试系统的一半,然后交换阵列并测试另一半:
// Shift B under A
A 01111111
B 11111110
B ... -->
B 11111110
// Shift A under B
B 11111110
A 01111111
A ... -->
A 01111111
如果你这样做,你会得到类似于O((A+B-2)*min(A,B)/2),或者更方便的O(N2)
因此,在我们在评论中讨论了它之后,我们一致认为问题在于为代码找到最坏的运行时。通过以下证明,我们可以声称它至少是ω(n^3): 让
A=aaaa…aabb…bbbb
意思是A=n
,它由n/2
A
和n/2
b
B=aaaa….
其中B=n
现在我们考虑最外循环的第一个<代码> N/2</Cult>迭代(即第一个<代码> N/2 <代码> A<代码>字符串的起始索引)。修复最外层循环的第一个
n/2
迭代中的一些迭代i
。第二个循环的上限至少为n-n/2=n/2
,因为两个字符串的LCS的长度为n/2
。对于第二个循环的每次迭代,我们匹配一个长度为n/2-i
的字符串(您可以用矛盾来证明这一点)。因此,在最外层循环的第一次n/2
迭代之后,该行:
longest\u length\u found=longest\u length\u found>偏移量?找到的最长长度:偏移量代码>
已运行:
n/2*(n/2)+n/2*(n/2-1)+n/2*(n/2-2)+……+n/2*(2)+n/2*(1)=n/2*ω(n^2)=ω(n^3)
具体来说,对于最外层循环的第一次迭代,我们在字符串a
中有一个n/2
a
,在B
中有n/2
起始点。对于B
中的每个起始点,我们将匹配长度为n/2
的完整公共子字符串(这意味着我们将命中该行n/2
次)。这就是n/2*(n/2)
。对于最外层循环的下一次迭代,我们在字符串a
中有一个n/2-1
a
,在B
中仍然有n/2
起始点。在这种情况下,我们为每个起始索引=>n/2(n/2-1)
匹配一个长度为n/2-1
的公共子字符串。同一个参数在i=n/2
中起归纳作用
无论如何,我们知道算法在输入端的运行时间比最外层循环的第一次n/2
迭代的运行时间要长,所以它也是Omega(n^3)
通常,当我们谈论运行时复杂性时,我们谈论一些情况,例如“平均情况”、“最坏情况”或“预期运行时”(在随机算法的情况下)。这里,您似乎已经将可能的输入划分为任意集合,并说“这在O(n^3)
中运行,而这在O(n^2)
中运行。”.我只是建议,也许有必要再研究一下定义,因为这个问题的定义有点模糊。我还想说,如果字符串相等,它就不是线性的……但这取决于你的成本模型。我认为答案是,除非我能优化到字符串长度的平方根,运行时并没有变得更好——优化只会减少最坏的情况。是的,我试图了解最坏的情况是什么,最坏的情况是什么数据集。我更新了问题,使其更具体。我非常确定,如果字符串相等,它是线性的。当a_索引和b_索引都为0时,第一次er most for循环的偏移量从0到字符串的长度,每次迭代都会做恒定的功。在这一点上,找到的最长的_length_将设置为字符串的长度,导致第二个for循环终止,然后外部for循环也从1开始终止!<0(字符串长度-本身)我没有注意到你从循环的上限中减去了最长的长度。我认为这不正确……如果B
中最长的公共子序列(意思是B
中的副本)会怎么样以B
中的最后一个字符结尾?基本上,A=asbb
和B=axbb
对于相同的字符串,我的算法有一个明确的O(N)的最佳情况。“几乎你会发现的第一件事是从A[1]和B[0]开始的最大匹配子字符串,然后你将测试精确重叠的部分,从A开始[2] ,B[1]“--那不是真的。它将在找到一个subs后立即停止
// Shift B under A
A 01111111
B 11111110
B ... -->
B 11111110
// Shift A under B
B 11111110
A 01111111
A ... -->
A 01111111
int lcs_half(char * A, char * B)
{
int maxlen = 0, len = 0;
int offset, i;
for( offset = 0; B[offset]; offset++ )
{
len = 0;
for( i = 0; A[i] && B[i+offset]; i++ )
{
if( A[i] == B[i+offset] ) {
len++;
if( len > maxlen ) maxlen = len;
}
else len = 0;
}
}
return maxlen;
}
int lcs(char * A, char * B)
{
int run1 = lcs_half(A,B);
int run2 = lcs_half(B,A);
return run1 > run2 ? run1 : run2;
}