字符串匹配算法的大O符号

Big O Notation for string matching algo

本文关键字:符号 匹配算法 字符串      更新时间:2023-10-16

函数foo的大O表示法是什么?

int foo(char *s1, char *s2)
{
   int c=0, s, p, found;
   for (s=0; s1[s] != ''; s++)
   {
      for (p=0, found=0; s2[p] != ''; p++)
      {
         if (s2[p] == s1[s])
         {
            found = 1;
            break;
         }
      }
      if (!found) c++;
   }
   return c;
}

函数foo的效率是多少?

a) O(n!)

b) O(n^2)

c) O(n lg(base2)n)

d) O(n)

我会说O(MN)。。。?

它是O(n²),其中n=max(长度(s1),长度(s2))(可以在小于二次时间内确定-见下文)。让我们来看看教科书上的定义:

f(n)∈O(g(n)),如果存在正实数c和正整数n所有n>=n 的c g(n)

通过这个定义,我们可以看到n代表一个数字-在这种情况下,这个数字是传入的字符串的长度。然而,有一个明显的差异,因为这个定义只提供了一个单变量函数f(n),而在这里,我们清楚地传入了两个长度独立的字符串。因此,我们寻找大O的多变量定义。然而,正如Howell在"关于多变量渐近符号"中所证明的那样:

"为多变量函数定义big-O表示法是不可能的,因为它暗示了所有这些[通常假设的]性质。"

实际上,对于具有多个变量的大O有一个正式的定义,但这需要满足单个变量大O之外的额外约束,并且超出了大多数(如果不是全部)算法课程的范围。对于典型的算法分析,我们可以通过将所有变量绑定到限制变量n来有效地将函数简化为单个变量。在这种情况下,变量(特别是长度(s1)和长度(s2))显然是独立的,但可以将它们绑定:

方法1

Let x1 = length(s1)
Let x2 = length(s2)

该函数的最坏情况发生在没有匹配的情况下,因此我们执行x1*x2迭代。

因为乘法是可交换的,所以最坏情况的情况foo(s1,s2)==foo(s2,s1)的最坏情况。因此,在不失一般性的情况下,我们可以假设x1>=x2。(这是因为,如果x1<x2,我们可以通过以相反的顺序传递参数来获得相同的结果)。

方法2(如果您不喜欢第一种方法)

对于最坏的情况(s1和s2不包含公共字符),我们可以在循环迭代之前确定长度(s1)和长度(s2)(在.NET和Java中,确定字符串的长度是O(1),但在这种情况下是O(n)),将较大的值分配给x1,将较小的值分配给与x2。这里很清楚,x1>=x2。

对于这种情况,我们将看到,确定x1和x2的额外计算使这个O(n²+2n)。我们使用以下简化规则,可以在这里找到,将其简化为O(n平方):

如果f(x)是几个项的总和,则保留增长率最大的项,而省略所有其他项。

结论

对于n = x1(我们的极限变量),使得x1 >= x2,最坏的情况是x1 = x2。因此:f(x1) ∈ O(n²)

额外提示

对于所有张贴到SO的与大O符号有关的家庭作业问题,如果答案不是以下之一:

O(1)
O(log log n)
O(log n)
O(n^c), 0<c<1
O(n)
O(n log n) = O(log n!)
O(n^2)
O(n^c)
O(c^n)
O(n!)

那么问题是可能最好被发布到https://math.stackexchange.com/

在big-O表示法中,我们总是必须定义出现的变量的含义。除非我们定义n是什么,否则O(n)没有任何意义。通常,我们可以省略这些信息,因为它从上下文中很清楚。例如,如果我们说某个排序算法是O(n log(n)),那么n总是表示要排序的项目数,所以我们不必总是声明这一点。

big-O表示法的另一个重要之处是它只给出了一个上限——O(n)中的每个算法也在O(n^2)中。该符号通常被用作"算法具有表达式给定的精确渐近复杂度(高达常数因子)"的含义,但它的实际定义是"算法的复杂度受给定表达式(高达恒定因子)的限制"。

在您给出的示例中,您将mn分别作为两个字符串的长度。根据这个定义,该算法实际上是O(m n)。如果我们将n定义为两个字符串中较长字符串的长度,我们也可以将其写成O(n^2)——这也是算法复杂性的上限。与n的定义相同,该算法也是O(n!),但不是O(n)O(n log(n))

O(n^2)

就复杂性而言,函数的相关部分是嵌套循环。最大迭代次数是s1的长度乘以s2的长度,两者都是线性因子,因此最坏情况下的计算时间是O(n^2),即线性因子的平方。正如伊桑所说,O(mn)和O(n^2)实际上是一回事。

这样想:

有两个输入。如果函数只是返回,那么它的性能与参数无关。这将是O(1)。

如果函数在一个字符串上循环,则性能与该字符串的长度线性相关。因此O(N)。

但是函数在循环中有一个循环。性能与s1的长度和S2的长度有关。将这些长度相乘,得到循环迭代次数。它不再是线性的,它遵循一条曲线。这是O(N^2)。