最常见的子序列:为什么这是错的?

时间:2013-05-19 22:07:18

标签: c arrays algorithm valgrind lcs

int lcs(char * A, char * B)
{
  int m = strlen(A);
  int n = strlen(B);
  int *X = malloc(m * sizeof(int));
  int *Y = malloc(n * sizeof(int));
  int i;
  int j;
  for (i = m; i >= 0; i--)
    {
      for (j = n; j >= 0; j--)
        {
          if (A[i] == '\0' || B[j] == '\0') 
              X[j] = 0;
          else if (A[i] == B[j]) 
              X[j] = 1 + Y[j+1];
          else 
              X[j] = max(Y[j], X[j+1]);
        }
      Y = X;
    }
  return X[0];
}

这很有效,但是valgrind大声抱怨无效读取。我怎么弄乱记忆?对不起,我总是在C内存分配上失败。

4 个答案:

答案 0 :(得分:2)

这里的问题是你的桌子的大小。请注意,您将空间分配为

int *X = malloc(m * sizeof(int));
int *Y = malloc(n * sizeof(int));

但是,您使用索引0 ... m和0 ... n,这意味着X中需要m + 1个插槽,Y中需要n + 1个插槽。

尝试将其更改为阅读

int *X = malloc((m + 1) * sizeof(int));
int *Y = malloc((n + 1) * sizeof(int));

希望这有帮助!

答案 1 :(得分:1)

系列问题。首先,正如templatetypedef所说,你的分配不足。

然后,正如稻田所说,你没有释放你的malloc记忆。如果您需要Y=X行,则需要将原始malloc空间地址存储在另一组变量中,以便可以在其上调用free

...mallocs...
int * original_y = Y;
int * original_x = X;
...body of code...
free(original_y);
free(original_x);
return X[0];

但是这并没有解决你的新问题,这就是代码实际上不起作用的原因吗?

我承认我无法遵循您的代码(没有更多的研究),但我可以提出一种可行的算法,并且更容易理解。这可能有些 伪代码 并且不是特别有效,但是第一步是正确的。我稍后列出了一些优化。

int lcs(char * A, char * B)
{
  int length_a = strlen(A);
  int length_b = strlen(B);


  // these hold the position in A of the longest common substring
  int longest_found_length = 0;

  // go through each substring of one of the strings (doesn't matter which, you could pick the shorter one if you want)
  char * candidate_substring = malloc(sizeof(char) * length_a + 1);
  for (int start_position = 0; start_position < length_a; start_position++) {
    for (int end_position = start_position; end_position < length_a; end_position++) {

       int substring_length = end_position - start_position + 1;

       // make a null-terminated copy of the substring to look for in the other string
       strncpy(candidate_substring, &(A[start_position]), substring_length);
       if (strstr(B, candidate_substring) != NULL) {
         longest_found_length = substring_length;
       }
    }

  }
  free(candidate_substring);
  return longest_found_length;
}

您可以执行一些不同的优化:

       // if this can't be longer, then don't bother checking it.  You can play games with the for loop to not have this happen, but it's more complicated.
       if (substring_length <= longest_found_index) {
         continue;
       }

       // there are more optimizations you could do to this, but don't check
       //   the substring if it's longer than b, since b can't contain it.
       if (substring_length > length_b) {
         continue;
       } 

   if (strstr(B, candidate_substring) != NULL) {
     longest_found_length = end_position - start_position + 1;
   } else {
     // if nothing contains the shorter string, then nothing can contain the longer one, so skip checking longer strings with the same starting character
     break; // skip out of inner loop to next iteration of start_position
   }

您可以使用end_position + 1NUL字符进行字符交换,而不是将每个候选子字符串复制到新字符串。然后,在b中查找该子字符串后,将end_position+1处的原始字符交换回来。这会更快,但会使实现变得复杂一些。

答案 2 :(得分:1)

注意:我通常不会写两个答案,如果你觉得它很俗气,请随意评论这个并注意投票。这个答案是一个更优化的解决方案,但我想先给出一个我能想到的最简单的解决方案,然后将其放在另一个答案中,以免混淆两者。基本上它们适用于不同的受众。

有效解决这个问题的关键是,在寻找较长的子串时,不要丢弃有关较短公共子串的信息。天真地,你检查每个子串与另一个子串,但如果你知道“AB”匹配“ABC”,你的下一个字符是C,不要检查“ABC”是否在“ABC”中,只需检查“AB”之后的点是“C”。

对于A中的每个字符,你必须检查B中的所有字母,但是因为一旦更长的子字符串不再可能我们停止查看B,它会大大限制检查的数量。每次你预先获得更长的匹配时,你就会消除对后端的检查,因为它将不再是一个更长的子串。

例如,如果A和B都很长,但不包含公共字母,则A中的每个字母将与B中的每个字母进行比较,以表示A * B的运行时间。

对于存在大量匹配但序列长度不是较短字符串长度的很大一部分的序列,您可以使用A * B组合来检查两个字符串中较短的一个(A或B)导致A * B * A或A * B * B,对于相似长度的字符串基本上是O(n ^ 3)时间。我真的认为这个解决方案中的优化会比n ^ 3更好,即使有三个嵌套for循环,但它似乎不是我能说的最好。

不过,我正在考虑这个问题。要么找到的子串不是字符串长度的重要部分,在这种情况下优化不会做太多,但是A * B的每个组合的比较不能与A或B进行比例并且退出到常数 - 或 - 它们是A和B的重要部分,它直接划分为必须进行比较的A * B组合。

我可以在一个问题中问这个问题。

int lcs(char * A, char * B)
{
  int length_a = strlen(A);
  int length_b = strlen(B);

  // these hold the position in A of the longest common substring
  int longest_length_found = 0;

  // for each character in one string (doesn't matter which), look for incrementally larger strings in the other
  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++) {

       // offset into each string until end of string or non-matching character is found
      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;
}

答案 3 :(得分:0)

除了templatetypedef所说的,还有一些需要考虑的事情:

  • 为什么XY的大小不同?
  • 你为什么要做Y = X?这是指针的分配。您是否意味着memcpy(Y, X, (n+1)*sizeof(int))