Algorithm 大O,你如何计算/近似它?

Algorithm 大O,你如何计算/近似它?,algorithm,optimization,complexity-theory,big-o,performance,Algorithm,Optimization,Complexity Theory,Big O,Performance,大多数拥有计算机科学学位的人肯定知道什么。 它有助于我们衡量算法的伸缩性 但我很好奇,您如何计算或近似算法的复杂性?熟悉我使用的算法/数据结构和/或快速浏览迭代嵌套分析。困难在于当您调用库函数时,可能多次调用—您常常无法确定您是否在不必要的时候调用了该函数,或者他们正在使用什么实现。也许库函数应该有一个复杂度/效率度量,无论是大O还是其他度量,都可以在文档中找到,甚至可以在文档中找到。将算法分解成您知道的大O符号,并通过大O运算符组合。这是我知道的唯一方法 有关更多信息,请查看主题。大O给出了算

大多数拥有计算机科学学位的人肯定知道什么。 它有助于我们衡量算法的伸缩性


但我很好奇,您如何计算或近似算法的复杂性?

熟悉我使用的算法/数据结构和/或快速浏览迭代嵌套分析。困难在于当您调用库函数时,可能多次调用—您常常无法确定您是否在不必要的时候调用了该函数,或者他们正在使用什么实现。也许库函数应该有一个复杂度/效率度量,无论是大O还是其他度量,都可以在文档中找到,甚至可以在文档中找到。

将算法分解成您知道的大O符号,并通过大O运算符组合。这是我知道的唯一方法


有关更多信息,请查看主题。

大O给出了算法时间复杂度的上限。它通常与处理数据集(列表)结合使用,但也可以在其他地方使用

下面是一些在C代码中如何使用它的示例

假设我们有一个由n个元素组成的数组

int array[n];
如果我们想要访问数组的第一个元素,这将是O(1),因为不管数组有多大,总是需要相同的恒定时间来获取第一个项目

x = array[0];
如果我们想在列表中找到一个数字:

for(int i = 0; i < n; i++){
    if(array[i] == numToFind){ return i; }
}
for(int i=0;i
这将是O(n),因为我们最多需要查看整个列表才能找到我们的号码。Big-O仍然是O(n),尽管我们可能会在第一次尝试时找到我们的数字,并在循环中运行一次,因为Big-O描述了算法的上界(ω表示下界,θ表示紧界)

当我们进入嵌套循环时:

for(int i = 0; i < n; i++){
    for(int j = i; j < n; j++){
        array[j] += 2;
    }
}
for(int i=0;i
这是O(n^2),因为对于外环(O(n))的每一次传递,我们必须再次遍历整个列表,因此n的乘积为n的平方


这仅仅是触及表面,但当你开始分析更复杂的算法时,涉及证明的复杂数学就开始发挥作用。希望这至少能让你熟悉基本知识。

看到这里的答案,我想我们可以得出结论,我们中的大多数人确实可以通过查看算法并使用常识来近似算法的顺序,而不是像在大学时所想的那样,使用常识来计算。 话虽如此,我必须补充的是,即使是教授也鼓励我们(稍后)认真思考它,而不是仅仅计算它

另外,我想补充一下递归函数的实现方法:

假设我们有一个类似()的函数:

它递归地计算给定数字的阶乘

第一步是尝试并确定函数体的性能特征。仅在这种情况下,函数体中不做任何特殊操作,只进行乘法(或返回值1)

因此身体的性能是:O(1)(常数)

接下来,尝试并确定递归调用的数量。在这种情况下,我们有n-1个递归调用

因此递归调用的性能是:O(n-1)(顺序是n,因为我们丢弃了不重要的部分)

然后将这两个函数放在一起,就可以得到整个递归函数的性能:

1*(n-1)=O(n)


,来回答我在这里描述的方法实际上处理得很好。但请记住,这仍然是一个近似值,并不是一个完整的数学正确答案。这里描述的方法也是我们在大学里教授的方法之一,如果我没记错的话,它被用于比我在本例中使用的阶乘更高级的算法。

当然,这完全取决于您对函数体的运行时间和递归调用数的估计程度,但对于其他方法也是如此。

大O表示法很有用,因为它很容易使用,并且隐藏了不必要的复杂性和细节(对于不必要的定义)。计算分治算法复杂性的一个好方法是树方法。假设您有一个带有中值过程的快速排序版本,因此每次都将阵列分割为完全平衡的子阵列

现在,构建一个与您使用的所有阵列对应的树。在根节点上有原始数组,根节点有两个子节点,它们是子数组。重复此操作,直到底部有单元素数组为止

由于我们可以在O(n)时间内找到中位数,并在O(n)时间内将数组分成两部分,因此每个节点上完成的功为O(k),其中k是数组的大小。树的每个级别都包含(最多)整个数组,因此每个级别的工作量是O(n)(子数组的大小加起来是n,因为每个级别有O(k),所以我们可以加起来)。树中只有log(n)级别,因为每次我们将输入减半

因此,我们可以用O(n*log(n))来确定功的上限

然而,大O隐藏了一些我们有时不能忽视的细节。考虑用

计算斐波那契数列
a=0;
b=1;
for (i = 0; i <n; i++) {
    tmp = b;
    b = a + b;
    a = tmp;
}
a=0;
b=1;

对于(i=0;i基本上90%的时间都是在分析循环。有单循环、双循环、三循环嵌套吗?有O(n)、O(n^2)、O(n^3)运行时间


很少(除非您正在编写一个包含大量基本库(例如.NET BCL或C++的STL)的平台),否则您会遇到任何比查看循环(语句、while、goto等)更困难的情况。

小提示:
大O
符号用于表示渐进复杂性(即,当p的大小
a=0;
b=1;
for (i = 0; i <n; i++) {
    tmp = b;
    b = a + b;
    a = tmp;
}
int nCmp = 0;
System.Random rnd = new System.Random();

// measure the time required to sort a list of n integers
void DoTest(int n)
{
   List<int> lst = new List<int>(n);
   for( int i=0; i<n; i++ )
      lst[i] = rnd.Next(0,1000);

   // as we sort, keep track of the number of comparisons performed!
   nCmp = 0;
   lst.Sort( delegate( int a, int b ) { nCmp++; return (a<b)?-1:((a>b)?1:0)); }

   System.Console.Writeline( "{0},{1}", n, nCmp );
}


// Perform measurement for a variety of sample sizes.
// It would be prudent to check multiple random samples of each size, but this is OK for a quick sanity check
for( int n = 0; n<1000; n++ )
   DoTest(n);
int sum(int* data, int N) {
    int result = 0;               // 1

    for (int i = 0; i < N; i++) { // 2
        result += data[i];        // 3
    }

    return result;                // 4
}
Number_Of_Steps = f(N)
Number_Of_Steps = f(data.length)
f(N) = C + ??? + C
f(N) = C + (C + C + ... + C) + C = C + N * C + C
f(N) = 2 * C * N ^ 0 + 1 * C * N ^ 1
f(N) = 1 + N ^ 1
O(N)
for (i = 0; i < 2*n; i += 2) {  // 1
    for (j=n; j > i; j--) {     // 2
        foo();                  // 3
    }
}
f(N) = Summation(i from 1 to 2 * N / 2)( ... ) = 
     = Summation(i from 1 to N)( ... )
f(N) = Summation(i from 1 to N)( Summation(j = ???)(  ) )
f(N) = Summation(i from 1 to N)( Summation(j = 1 to (N - (i - 1) * 2)( C ) )
f(N) = Summation(i from 1 to N / 2)( Summation(j = 1 to (N - (i - 1) * 2)) * ( C ) ) + Summation(i from 1 to N / 2) * ( C )
f(N) = Summation(i from 1 to N / 2)( (N - (i - 1) * 2) * ( C ) ) + (N / 2)( C )

f(N) = C * Summation(i from 1 to N / 2)( (N - (i - 1) * 2)) + (N / 2)( C )

f(N) = C * (Summation(i from 1 to N / 2)( N ) - Summation(i from 1 to N / 2)( (i - 1) * 2)) + (N / 2)( C )

f(N) = C * (( N ^ 2 / 2 ) - 2 * Summation(i from 1 to N / 2)( i - 1 )) + (N / 2)( C )

=> Summation(i from 1 to N / 2)( i - 1 ) = Summation(i from 1 to N / 2 - 1)( i )

f(N) = C * (( N ^ 2 / 2 ) - 2 * Summation(i from 1 to N / 2 - 1)( i )) + (N / 2)( C )

f(N) = C * (( N ^ 2 / 2 ) - 2 * ( (N / 2 - 1) * (N / 2 - 1 + 1) / 2) ) + (N / 2)( C )

=> (N / 2 - 1) * (N / 2 - 1 + 1) / 2 = 

   (N / 2 - 1) * (N / 2) / 2 = 

   ((N ^ 2 / 4) - (N / 2)) / 2 = 

   (N ^ 2 / 8) - (N / 4)

f(N) = C * (( N ^ 2 / 2 ) - 2 * ( (N ^ 2 / 8) - (N / 4) )) + (N / 2)( C )

f(N) = C * (( N ^ 2 / 2 ) - ( (N ^ 2 / 4) - (N / 2) )) + (N / 2)( C )

f(N) = C * (( N ^ 2 / 2 ) - (N ^ 2 / 4) + (N / 2)) + (N / 2)( C )

f(N) = C * ( N ^ 2 / 4 ) + C * (N / 2) + C * (N / 2)

f(N) = C * ( N ^ 2 / 4 ) + 2 * C * (N / 2)

f(N) = C * ( N ^ 2 / 4 ) + C * N

f(N) = C * 1/4 * N ^ 2 + C * N
O(N²)
for (i = 0; i < n-1; i++) 
{
    small = i;
    for (j = i+1; j < n; j++)
        if (A[j] < A[small])
            small = j;
    temp = A[small];
    A[small] = A[i];
    A[i] = temp;
}
(1) for (j = 0; j < n; j++)
(2)   A[i][j] = 0;
(2) for (i = 0; i < n; i++)
(3)     for (j = 0; j < n; j++)
(4)         A[i][j] = 0;