【问题标题】:Code optimization for string comparisons字符串比较的代码优化
【发布时间】:2013-08-12 21:29:45
【问题描述】:

我有一个字符串比较的函数代码如下:

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>

int max=0;

int calcMis(char *string,int i, int j,int len)
{
     int mis=0;
     int k=0;
     while(k<len)
     {
             if(string[i+k]!=string[j+k])
                mis+=1;
             if((mis+len-k-1)<=max)
                 return 1;
             else if(mis>max)
                 return 0;
             k=k+1;
     }
}

int main()
{
    char *input=malloc(2000*sizeof(char));
    scanf("%d",&max);
    scanf("%s",input);
    int c=0,i,j,k,x,mis=0;
    int len=strlen(input);
    i=0;
    while(i<len-1)
    {
        j=i;
        while(j<len-1)
         {
             k=i+1;
             x=j-i+1;
             if(x<=max)
                 c=c+len-k-x+1;
             else
                while(k+x<=len)
                {
                  if(strncmp(input+i,input+k,x+1)==0)
                   {
                      if(max>=0)
                          c=c+x;
                   }
                  else
                   c+=calcMis(input,i,k,x);
                  k=k+1;
                }       
            j=j+1;
         }
        i=i+1;
    }   
    printf("%d",c);
    return 0;   
}  

此代码是问题的解决方案:

给定一个字符串 S 和整数 K,找到等于的整数 C 子串对的数量 (S1,S2) 使得 S1 和 S2 有 等长且 Mismatch(S1, S2)

eg : abc 那么子字符串是 {a,ab,abc,b,b​​c,c}

还有比这更好的方法吗?这段代码有什么可能的优化吗?

【问题讨论】:

  • 好吧,我认为没有理由让接口依赖于两个子字符串在同一个超字符串中。为什么不只取两个指针参数并从调用者传递string+istring+j?这更通用,并将工作数据集减少了 1。
  • 为什么不使用strncmp,第一个和第二个参数是:str+istr+j
  • @Tuntuni:我需要计算不匹配,而不仅仅是字符串是否按字母顺序出现在之前或之后
  • 这是让我呕吐的接口之一,因为它需要char * 并且根本不修改它。如果这是 C++,它也会使用std::string,并且可以使用std::mismatch
  • 如果您使用的是 C 风格的字符串,为什么要将此问题标记为 C++?如果你使用 C++,为什么不使用std::string

标签: c string algorithm


【解决方案1】:

注意:此分析是在他/她编辑帖子并包含他/她的其余代码之前进行的。他/她在原始帖子中没有提及main 函数(我在其中提供了我的答案)。


查看calcMis 的代码,以下是我将进行的一些可读性和样式改进:

  • 从循环中删除所有返回语句。对于小循环来说没什么大不了的,但对于大循环来说就更重要了,因为当它有 3 或 4 个额外的情况要离开循环时,调试起来就更困难了。
  • 根据函数的作用重新定义参数。

您的算法按n 的顺序运行,但我们可以减少它执行的一些操作。我对你的算法的分析如下:

assignment operator            (=)  x4: O(1)
while loop                          x1: O(n), where n is len.
  dereference operator           (*)  x2: O(1)
  less than operator             (<)  x1: O(1)
  does not equal operator        (!=) x1: O(1)
  addition operator              (+)  x4: O(1)
  subtraction operator           (-)  X2: O(1)
  less than or equal to operator (<=) x1: O(1)
  Order: O(n) + 2 * O(1) + O(1) + O(1) + 4 * O(1) + 2 * O(1) + 1 * O(1) = O(n)
Order: 4 * O(1) + O(n) = O(n)

这是改进的算法(微效率和可读性改进)——仍然是线性顺序,但指令更少,并利用了编译器的 const 优化:

bool calcMis( char const * const str, int const i, int const j, int const len ) {
  // Checks pre conditions.
  assert( str != NULL );

  // Determines if the length is 0, if so return 0 mismatches.
  if ( len == 0 ) return true;

  // Determines if we are comparing at the same index, if so return 0 mismatches.
  if ( i == j ) return true;

  // Defines an integer mis, holds the number of mismatches.
  int mis = 0;

  // Iterates over the entire string of length len.
  for ( int k = 0; ( k < len ) && ( mis < max ); k++ ) {
    // Determines whether there was a mismatch at positions i and j.
    if ( str[ i + k ] != str[ j + k ] ) mis += 1;
  }

  // Defines a bool result, determines whether we have had too many mismatches.
  bool const result = !( mis > max );

  return result;
}

【讨论】:

  • mis += 1 -> mis++
  • @H2CO3,有那个存在的意义吗? (我很清楚这一点)
  • calcMis 的复杂度为 O(n),但它是从嵌套循环(3 级深)中调用的,因此该算法在 O(n^4) 中运行(不确定最后一部分,但是绝对不是 O(n))。它需要彻底重新设计,而不是提高可读性。
  • @anatolyg,我的回复是在他编辑他的原始帖子并包含他的其余代码之前做出的。在我分析之前,他没有提到这一点。
  • 对编译器感到羞耻,因为它无法弄清楚这些值是常量.. :/
【解决方案2】:

这是一个可能会有所帮助的想法。首先,比较字符串中的所有字符对:

void compare_all(char* string, int length, int* comp)
{
    for (int i1 = 0; i1 < length; ++i1)
        for (int i2 = 0; i2 < length; ++i2)
            result[i1 * length + i2] = (string[i1] != string[i2]);
}

这里comp 表示一个包含值 0 和 1 的方阵。每对子字符串对应于该矩阵中的对角线部分。例如,对于字符串“testing”,矩阵的以下部分表示子字符串“tes”和“tin”。

. . . O . . .
. . . . O . .
. . . . . O .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .

您必须计算有多少部分的元素总和不超过k。为此,请一一检查与主对角线平行的所有对角线。为了不重复计算,请仅查看低于(或高于)主对角线的那些(为简单起见,我们将主对角线包括在内)。

int count_stuff(int* comp, int n, int k)
{
    int result = 0;
    for (diag = 0; diag < n; ++diag)
    {
        int* first_element_in_diagonal = comp + diag;
        int jump_to_next_element = n + 1;
        int length_of_diagonal = n - diag;
        result += count_stuff_on_diagonal(
            first_element_in_diagonal,
            jump_to_next_element,
            length_of_diagonal,
            k);
    }
    return result;
}

现在,问题要简单得多:找出整数序列中的部分数,其总和不大于k。最直接的方法是枚举所有这些部分。

int count_stuff_on_diagonal(int* comp, int jump, int n, int k)
{
    int result = 0;
    for (int i1 = 0; i1 < n; ++i1)
        for (int i2 = i1 + 1; i2 < n; ++i2)
        {
            int* first_element_in_section = comp + i1 * jump;
            int mismatches = count_sum_of_section(
                first_element_in_section,
                jump,
                i2 - i1);
            if (mismatches <= k)
                ++result;
        }
    return result;
}

为了提高计算一段连续整数之和的速度,建一个cumulative sums的表;在 0 和 1 的矩阵上使用它。

(请原谅我没有使用const 和 VLA,偶尔会出现语法错误)。

【讨论】:

  • 主要问题是 - 你的算法的复杂度是多少?
  • 好,你有没有想过 O(n^2*k) 的解决方案?我相信它应该使用 DP 存在。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-26
  • 2014-09-02
  • 2013-05-02
  • 2016-04-27
  • 1970-01-01
  • 2015-10-25
  • 1970-01-01
相关资源
最近更新 更多