如何找到算法的时间复杂度

How to find time complexity of an algorithm

问题

如何确定算法的时间复杂度?

在发布问题之前我做了什么?

我已经浏览过这个,这个和其他许多链接

但在哪里我能找到一个清晰而直接的解释,如何计算时间复杂性。

我知道什么?

说一个和下面一样简单的代码:

1
2
char h = 'y'; // This will be executed 1 time
int abc = 0; // This will be executed 1 time

比如说下面的循环:

1
2
3
for (int i = 0; i < N; i++) {        
    Console.Write('Hello World !');
}

int i=0;这将只执行一次。实际计算时间为i=0,而不是申报时间。

i

I++;将执行N次

所以这个循环所需的操作数是

1+(n+1)+n=2n+2

注意:这可能仍然是错误的,因为我对自己对计算时间复杂性的理解不太自信。

我想知道什么?

好吧,我想我知道这些小的基本计算,但在大多数情况下,我认为时间的复杂性是

O(n),O(n2),O(logn),O(n!)…还有许多其他的,

有人能帮我理解如何计算算法的时间复杂度吗?我相信有很多像我这样的新手想知道这一点。


这是一篇优秀的文章:http://www.daniweb.com/software-development/computer-science/threads/13488/time-complexity-of-algorithm

下面的答案是从上面复制的(以防优秀的链接崩溃)

计算时间复杂性最常用的度量是大O符号。这将删除所有常量因子,以便在n接近无穷大时,可以相对于n估计运行时间。一般来说,你可以这样想:

1
statement;

是常量。语句的运行时间相对于n不会改变。

1
2
for ( i = 0; i < N; i++ )
     statement;

是线性的。循环的运行时间与n成正比。n加倍时,运行时间也成正比。

1
2
3
4
for ( i = 0; i < N; i++ ) {
  for ( j = 0; j < N; j++ )
    statement;
}

是二次的。两个回路的运行时间与n的平方成正比,当n加倍时,运行时间增加n*n。

1
2
3
4
5
6
7
8
while ( low <= high ) {
  mid = ( low + high ) / 2;
  if ( target < list[mid] )
    high = mid - 1;
  else if ( target > list[mid] )
    low = mid + 1;
  else break;
}

是对数的。该算法的运行时间与N可被除以2的次数成正比。这是因为算法将工作区域划分为每次迭代的一半。

1
2
3
4
5
6
void quicksort ( int list[], int left, int right )
{
  int pivot = partition ( list, left, right );
  quicksort ( list, left, pivot - 1 );
  quicksort ( list, pivot + 1, right );
}

是n*log(n)。运行时间由N个循环(迭代或递归)组成,对数为对数,因此该算法是线性和对数的组合。

一般来说,对一个维度中的每一项做一些事情是线性的,对两个维度中的每一项做一些事情是二次的,将工作区域分成两部分是对数的。还有其他大的O度量,如立方、指数和平方根,但它们并没有那么常见。大O符号表示为O(),度量单位在哪里。快速排序算法将被描述为O(n*log(n))。

注意,所有这些都没有考虑到最佳、平均和最坏情况的度量。每个都有自己的大O符号。还要注意,这是一个非常简单的解释。大O是最常见的,但它也更复杂,我已经展示了。还有其他符号,如大欧米伽、小欧米伽和大西塔。在算法分析课程之外,您可能不会遇到它们。;)


How to find time complexity of an algorithm

将它作为输入大小的函数执行的机器指令数量相加,然后将表达式简化为最大值(当n非常大时),并且可以包括任何简化常量因子。

例如,让我们看看如何简化2N + 2机器指令,将其描述为O(N)

为什么要删除两个2

当n变大时,我们对算法的性能感兴趣。

考虑两个术语2n和2。

当n变大时,这两个项的相对影响是什么?假设n是一百万。

那么第一学期是200万,第二学期只有2万。

基于这个原因,我们放弃了大N的所有条款,除了最大的条款。

所以,现在我们已经从2N + 2转到2N

传统上,我们只对不变因素的性能感兴趣。

这意味着,当n很大时,我们并不真正关心性能是否存在一些常数倍的差异。2n的单位在一开始就没有很好的定义。所以我们可以乘或除一个常数因子,得到最简单的表达式。

所以2N变成了N


从这里开始-介绍算法的时间复杂度

1。介绍

在计算机科学中,算法的时间复杂度量化了算法作为表示输入的字符串长度的函数所花费的时间量。

2。大O符号

算法的时间复杂度通常用大O表示法来表示,它不包括系数和低阶项。当以这种方式表达时,据说时间复杂性被渐近地描述,即,随着输入大小趋于无穷大。

例如,如果一个算法对大小为n的所有输入所需的时间至多为5n3+3n,则渐进时间复杂性为o(n3)。以后再谈。

再举几个例子:

  • 1=O(n)
  • n=O(n2)
  • log(n)=O(n)
  • 2 n+1=O(n)

三。o(1)恒定时间:

如果一个算法需要相同的时间,不管输入的大小,它都被称为在恒定时间内运行。

实例:

  • 数组:访问任何元素
  • 固定大小堆栈:推送和弹出方法
  • 固定大小队列:排队和出列方法

4。O(N)线性时间

如果一个算法的时间执行与输入大小成正比,即时间随着输入大小的增加呈线性增长,则称该算法在线性时间内运行。

考虑下面的例子,下面我是线性搜索一个元素,它的时间复杂度为o(n)。

1
2
3
4
5
6
7
8
9
int find = 66;
var numbers = new int[] { 33, 435, 36, 37, 43, 45, 66, 656, 2232 };
for (int i = 0; i < numbers.Length - 1; i++)
{
    if(find == numbers[i])
    {
        return;
    }
}

更多的例子:

  • 数组:线性搜索、遍历、查找最小值等
  • arraylist:包含方法
  • 队列:包含方法

5。O(对数N)对数时间:

如果一个算法的执行时间与输入大小的对数成比例,则称其在对数时间内运行。

示例:二进制搜索

回想一下"20个问题"的游戏——任务是猜测一个区间内隐藏数字的值。每次你猜测时,都会被告知你的猜测是过高还是过低。20个问题游戏意味着一个策略,使用你的猜测数减半的间隔大小。这是一个被称为二进制搜索的一般问题解决方法的例子。

6。O(n2)二次时间

如果一个算法的时间执行与输入大小的平方成正比,则称它是在二次时间中运行的。

实例:

  • 冒泡排序
  • 选择排序
  • 插入排序

7。一些有用的链接

  • 大错特错
  • 确定算法的复杂性
  • 大O型备忘单


尽管这个问题有一些很好的答案。我想在这里用几个loop的例子给出另一个答案。

  • o(n):如果循环变量的增量/减量为常量,则循环的时间复杂性被视为o(n)。例如,以下函数具有O(N)时间复杂性。

    1
    2
    3
    4
    5
    6
    7
    8
    // Here c is a positive integer constant  
    for (int i = 1; i <= n; i += c) {  
        // some O(1) expressions
    }

    for (int i = n; i > 0; i -= c) {
        // some O(1) expressions
    }
  • O(n^ c):嵌套循环的时间复杂度等于执行最内层语句的次数。例如,下面的样本循环具有O(n ^ 2)时间复杂度

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    for (int i = 1; i <=n; i += c) {
       for (int j = 1; j <=n; j += c) {
          // some O(1) expressions
       }
    }

    for (int i = n; i > 0; i += c) {
       for (int j = i+1; j <=n; j += c) {
          // some O(1) expressions
    }

    例如,选择排序和插入排序具有O(n ^ 2)时间复杂度。

  • O(Logn)循环的时间复杂度被认为是O(Logn),如果循环变量被除以/乘以恒定量。

    1
    2
    3
    4
    5
    6
    for (int i = 1; i <=n; i *= c) {
       // some O(1) expressions
    }
    for (int i = n; i > 0; i /= c) {
       // some O(1) expressions
    }

    例如二进制搜索具有O(Logn)时间复杂度。

  • o(loglogn)如果循环变量以指数形式减少/增加一个常量,则循环的时间复杂度被视为o(loglogn)。

    1
    2
    3
    4
    5
    6
    7
    8
    // Here c is a constant greater than 1  
    for (int i = 2; i <=n; i = pow(i, c)) {
       // some O(1) expressions
    }
    //Here fun is sqrt or cuberoot or any other constant root
    for (int i = n; i > 0; i = fun(i)) {
       // some O(1) expressions
    }

时间复杂性分析的一个例子

1
2
3
4
5
6
7
8
9
10
int fun(int n)
{    
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j < n; j += i)
        {
            // Some O(1) task
        }
    }    
}

分析:


For i = 1, the inner loop is executed n times.
For i = 2, the inner loop is executed approximately n/2 times.
For i = 3, the inner loop is executed approximately n/3 times.
For i = 4, the inner loop is executed approximately n/4 times.
…………………………………………………….
For i = n, the inner loop is executed approximately n/n times.

因此,上述算法的总时间复杂度为(n + n/2 + n/3 + … + n/n),变为n * (1/1 + 1/2 + 1/3 + … + 1/n)

关于系列(1/1 + 1/2 + 1/3 + … + 1/n)的重要内容是等于o(logn)。因此,上述代码的时间复杂度为o(nlogn)。

裁判:一二三


时间复杂度示例

1-基本操作(算术、比较、访问数组元素、赋值):运行时间始终为常量o(1)

例子:

1
2
3
read(x)                               // O(1)
a = 10;                               // O(1)
a = 1.000.000.000.000.000.000         // O(1)

2-if-then-else语句:仅从两个或更多可能的语句中占用最大运行时间。

例子:

1
2
3
4
5
6
7
age = read(x)                               // (1+1) = 2
if age < 17 then begin                      // 1
      status ="Not allowed!";              // 1
end else begin
      status ="Welcome! Please come in";   // 1
      visitors = visitors + 1;              // 1+1 = 2
end;

因此,上述伪码的复杂度为t(n)=2+1+max(1,1+2)=6。因此,它的大oh仍然是常数t(n)=o(1)。

3-循环(for,while,repeat):此语句的运行时间是循环数乘以循环内的操作数。

例子:

1
2
3
4
5
total = 0;                                  // 1
for i = 1 to n do begin                     // (1+1)*n = 2n
      total = total + i;                    // (1+1)*n = 2n
end;
writeln(total);                             // 1

所以,它的复杂性是t(n)=1+4n+1=4n+2。因此,t(n)=o(n)。

4-嵌套循环(循环内循环):由于主循环内至少有一个循环,所以此语句的运行时间使用o(n^2)或o(n^3)。

例子:

1
2
3
4
5
6
for i = 1 to n do begin                     // (1+1)*n  = 2n
   for j = 1 to n do begin                  // (1+1)n*n = 2n^2
       x = x + 1;                           // (1+1)n*n = 2n^2
       print(x);                            // (n*n)    = n^2
   end;
end;

常用运行时间

分析算法时有一些常见的运行时间:

  • O(1)–恒定时间常量时间是指运行时间是常量,不受输入大小的影响。

  • O(N)–线性时间当一个算法接受n个输入大小时,它也将执行n个操作。

  • o(log n)–对数时间运行时间为O(log n)的算法比O(n)稍快。通常,算法将问题分为大小相同的子问题。示例:二进制搜索算法、二进制转换算法。

  • o(n log n)–线性时间这种运行时间通常出现在"分治算法"中,它将问题递归地划分为子问题,然后在n次中合并。示例:合并排序算法。

  • O(n2)–二次时间看气泡排序算法!

  • O(n3)–立方时间其原理与O(n2)相同。

  • o(2n)–指数时间当输入变大时,速度非常慢,如果n=1000.000,t(n)将为21000.000。蛮力算法有这个运行时间。

  • O(n)!阶乘时间最慢的!!!!旅行推销员问题(TSP)

  • 摘自本文。解释得很好,应该读一读。


    当你分析代码时,你必须一行一行地分析它,计算每一个操作/识别时间复杂性,最后,你必须对它求和才能得到完整的图像。

    例如,您可以有一个具有线性复杂度的简单循环,但稍后在同一个程序中,您可以有一个具有三次复杂度的三次循环,因此您的程序将具有三次复杂度。生长的功能顺序在这里起作用。

    让我们看看算法的时间复杂性有哪些可能,您可以看到上面提到的增长顺序:

    • 常数时间有一个增长顺序,例如:a = b + c

    • 对数时间有一个增长的顺序,通常发生在当你把某个东西分成两半(二进制搜索、树、甚至循环),或者用同样的方法乘以某个东西。

    • 线性,生长顺序为N,例如

      1
      2
      3
      int p = 0;
      for (int i = 1; i < N; i++)
        p = p + 2;
    • 线性化,增长顺序为n*logN,通常出现在分治算法中。

    • cubic,order of growth N^3,经典的例子是一个三重循环,您可以检查所有三重循环:

      1
      2
      3
      4
      5
      int x = 0;
      for (int i = 0; i < N; i++)
         for (int j = 0; j < N; j++)
            for (int k = 0; k < N; k++)
                x = x + 2
    • 指数增长顺序2^N,通常在进行详尽搜索时发生,例如检查某些集合的子集。


    不严格地说,时间复杂性是一种总结算法的操作数或运行时间如何随着输入大小的增加而增长的方法。

    像生活中的大多数事情一样,鸡尾酒会可以帮助我们理解。

    o(n)

    当你到达聚会的时候,你必须和每个人握手(对每个项目都做一个操作)。随着参加人数的增加,你和大家握手的时间/工作会随着参加人数的增加而增加。

    为什么是O(N)而不是cN

    与人握手所需的时间有所不同。你可以求出平均值,然后用一个常数c捕捉它。但是这里的基本操作——与所有人握手——总是与O(N)成正比,不管c是什么。在讨论我们是否应该去参加鸡尾酒会时,我们通常更感兴趣的是我们必须会见每个人,而不是那些会议的细节细节。

    O(n ^ 2)

    鸡尾酒会的主持人要你玩一个愚蠢的游戏,每个人都会遇到其他人。因此,你必须会见其他人,因为下一个人已经见过你,他们必须会见其他人,等等。这个系列的总和是x^2/2+x/2。随着参加者人数的增加,x^2这个术语变得越来越大,所以我们放弃了其他的一切。

    O(n ^ 3)

    你必须和其他人见面,在每次会议期间,你必须和房间里的其他人谈谈。

    O(1)

    主持人想宣布一些事情。他们敲着酒杯大声说话。每个人都听到了。事实证明,不管有多少与会者,这个操作总是花费相同的时间。

    O(log n)

    主人按字母顺序把每个人都安排在桌上。丹在哪里?你认为他一定在亚当和曼迪之间(当然不是曼迪和扎克之间!)鉴于此,他是在乔治和曼迪之间吗?不,他一定在亚当和弗雷德之间,辛迪和弗雷德之间。等等…我们可以通过看一半的布景和一半的布景来有效地定位丹。最后,我们来看看O(对数)个体。

    O(n log n)

    你可以用上面的算法找到坐在桌子上的位置。如果有大量的人一次一个人走到桌子前,所有人都这样做了,那就需要O(n logn)时间。结果是,当必须对任何项集合进行比较时,对它们进行排序需要多长时间。

    最佳/最坏情况

    你来参加派对需要找到Inigo -要花多长时间?这取决于你什么时候到达。如果每个人都在闲逛,那么你就遇到了最坏的情况:它会占用EDCX1 1的时间。然而,如果每个人都坐在桌旁,它只需要EDOCX1,12的时间。或者你可以利用主机的酒杯呼喊威力,它只需要EDCOX1,13的时间。

    假设主机不可用,我们可以说,IGIGO查找算法具有EDOCX1×12的下界和EDOCX1×1的上界,取决于到达时的状态。

    空间与通信

    同样的想法也可以用于理解算法如何使用空间或通信。

    克努斯写了一篇关于前者的好论文,题为"歌曲的复杂性"。

    Theorem 2: There exist arbitrarily long songs of complexity O(1).

    PROOF: (due to Casey and the Sunshine Band). Consider the songs Sk defined by (15), but with

    1
    2
    V_k = 'That's the way,' U 'I like it, ' U
    U   = 'uh huh,' 'uh huh'

    for all k.


    我知道这个问题可以追溯到很久以前,这里有一些很好的答案,尽管如此,我还是想为那些在这篇文章中会绊倒的数学头脑的人再分享一点。主定理是研究复杂性时另一个有用的知识。我没有在其他答案中看到它。


    o(n)是用于编写算法时间复杂度的大o符号。当你把一个算法中执行的次数加起来,你会得到一个表达式,结果是2n+2,在这个表达式中,n是主要的项(如果它的值增加或减少,对表达式影响最大的项)。现在,o(n)是时间的复杂性,而n是支配性术语。例子

    1
    2
    3
    4
    For i= 1 to n;
      j= 0;
    while(j<=n);
      j=j+1;

    这里,内环的执行总数是n+1,外环的执行总数是n(n+1)/2,所以整个算法的执行总数是n+1+n(n+1/2)=(n^2+3n)/2。这里n^2是主要术语,因此该算法的时间复杂度为o(n^2)