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


889

问题

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

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

我经历了这个这个和许多其他链接

但是,在任何地方我都无法找到关于如何计算时间复杂度的清晰直接的解释。

我知道什么 ?

说一个简单的代码如下:

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

说一个像下面这样的循环:

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

int i = 0; 这将仅执行一次。时间实际上是计算到的,i=0而不是声明的。

我<N; 这将执行N + 1

我++; 这将执行N

因此,此循环所需的操作数为

{1+(N + 1)+ N} = 2N + 2

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

我想知道什么?

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

O(N),O(N2),O(log n)的,为O(n!) ......和许多其他

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


138
那些有兴趣的人可获得的奖励:大O 备忘
msanford 2013年

4
查看此博客:mohalgorithmsorbit.blogspot.com。它涵盖了递归算法(尤其是迭代算法)。
Mohamed Ennahdi El Idrissi 2015年

1
为什么使用Console.Write('Hello World!'); 不是机器指令?


1
@Chetan如果您的意思是Console.Write在计算复杂度时应该考虑这一点,这是正确的,但在这种情况下也没有什么关系,因为那只会改变一个常量因子,big-O会忽略它(请参阅答案),因此最终结果仍然是O(N)的复杂度。
伯恩哈德·巴克

Answers:


394

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

您将根据输入大小来执行多少条机器指令,然后将表达式简化为最大(当N非常大时)项,并且可以包括任何简化的常数因子。

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

我们为什么要删除两个2

随着N变大,我们对算法的性能感兴趣。

考虑两个项2N和2。

随着N变大,这两个项的相对影响是什么?假设N是一百万。

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

因此,对于大的N,我们除最大项外都舍弃了所有项。

因此,现在我们从2N + 2转到2N

传统上,我们只对直到恒定因素的性能感兴趣。

这意味着当N很大时,我们实际上并不关心性能差异是否存在恒定的倍数。最初,2N的单位定义不明确。因此,我们可以乘以或除以一个常数因子以获得最简单的表达式。

因此2N成为正义N


53
嘿,谢谢您让我知道“为什么O(2N + 2)转换为O(N)”,但这只是更大问题的一部分,我希望有人指出指向隐藏资源或总的来说,我想知道如何以O(N),O(n2),O(log n),O(n!)等时间复杂性结束。我知道我可能会问很多,但是我仍然可以尝试:{)
Yasser Shaikh 2012年

3
括号中的复杂度只是算法需要多长时间,使用我已经说明的方法简化了。我们只需将要执行的机器指令数量相加即可得出算法所需的时间。我们可以通过仅查看最繁忙的循环并除以常数来简化,正如我所解释的。
Andrew Tomazos 2012年

4
给出答案示例对于将来的读者会大有帮助。仅仅交出我必须注册的链接,当我只想浏览一些讲得很好的文字时,确实对我没有帮助。
bad_keypoints '16

2
如果您想了解DS和时间复杂度方面的知识,建议您观看Naveen Garg博士(印度理工学院德里教授)的视频。请查看链接。nptel.ac.in/courses/106102064
Rohit Bandil

2
(续)此层次结构的高度约为logN。至于O(N!),我的类推法不太可能将其切掉,但是排列按该顺序进行-陡峭地陡峭,比任何多项式或指数的。恰好有10个!六周之内的秒数,但宇宙少于20秒!秒。
John P

389

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

下面的答案是从上面复制的(如果优秀的链接破产了)

计算时间复杂度最常用的指标是Big O符号。这消除了所有恒定因素,因此当N接近无穷大时,可以相对于N估算运行时间。通常,您可以这样认为:

statement;

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

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

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

for ( i = 0; i < N; i++ ) {
  for ( j = 0; j < N; j++ )
    statement;
}

是二次方的。两个循环的运行时间与N的平方成正比。当N翻倍时,运行时间增加N *N。

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的次数成正比。这是因为该算法在每次迭代中将工作区域分为两半。

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个对数的循环(迭代或递归)组成,因此该算法是线性和对数的组合。

通常,对一维中的每个项目执行某项操作是线性的,对二维中每一个项目执行某项操作是二次项,将工作区域分为两半是对数的。还有其他Big O度量,例如三次方,指数和平方根,但它们并不常见。大O符号被描述为O ( <type> )其中<type>是度量。快速排序算法将描述为O ( N * log ( N ) )

请注意,所有这些都未考虑最佳,平均和最坏情况下的度量。每个都有自己的Big O表示法。另请注意,这是一个非常简单的解释。大O是最常见的,但我已经展示过它更加复杂。还有其他符号,例如大欧米茄,小o和大theta。在算法分析课程之外,您可能不会遇到它们。;)


10
在最坏的情况下,快速排序算法的运行时间为N ^ 2,尽管这种行为很少见。
nbro

2
IIRC,o和big omega用于最佳和平均情况下的复杂度(大o是最坏情况),因此“最佳,平均和最坏情况度量。每个都有自己的Big O表示法。” 将是不正确的。甚至还有更多具有更具体含义的符号,CS并不总是使用最合适的符号。我以Landau符号 btw 的名字来学习所有这些。无论如何+1 b / c最佳答案。
hiergiltdiestfu

@hiergiltdiestfu Big-O,Big-Omega等可以应用于算法的最佳,平均或最差情况下的运行时间。O和Ω与最坏情况和最佳情况有何关系?
伯恩哈德·巴克

另外,如果有人正在寻找如何通过任何方法计算大O的信息:stackoverflow.com/a/60354355/4260691
OhadM

最好的解释之一。
Shivaraj Patil

172

摘自此处- 算法时间复杂度简介

1.简介

在计算机科学中,算法的时间复杂度根据代表输入的字符串的长度来量化算法运行所花费的时间。

2.大O符号

算法的时间复杂度通常使用大O表示法表示,其中不包括系数和低阶项。当以这种方式表示时,时间复杂度被称为渐近描述,即,随着输入大小达到无穷大。

例如,如果算法在大小为n的所有输入上所需的时间最多为5n 3 + 3n,则渐近时间复杂度为O(n 3)。以后再说。

其他几个例子:

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

3. O(1)恒定时间:

如果算法需要相同的时间量,则无论输入大小如何,都可以说算法以恒定的时间运行。

例子:

  • 数组:访问任何元素
  • 固定大小的堆栈:push和pop方法
  • 固定大小的队列:入队和出队方法

4. O(n)线性时间

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

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

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(log n)对数时间:

如果算法的时间执行与输入大小的对数成正比,则称该算法以对数时间运行。

示例:二进制搜索

回想一下“二十个问题”游戏-任务是猜测间隔中隐藏数字的值。每次您进行猜测时,都会告诉您您的猜测是太高还是太低。二十个问题的游戏蕴含一种策略,该策略使用您的猜测数字将间隔大小减半。这是称为二分查找的一般问题解决方法的示例

6. O(n 2)二次时间

如果算法的时间执行与输入大小的平方成正比,则称该算法以二次时间运行。

例子:

7.一些有用的链接


17
注意:第一个链接已断开。
Ziezi

2
O(n2)应该写为O(n ^ 2)以避免混淆。
Rizki Hadiaturrasyid

100

尽管对于这个问题有一些好的答案。我想在这里再举几个例子loop

  • O(n):如果循环变量递增/递减恒定量,则循环的时间复杂度被视为O(n)。例如,以下函数的时间复杂度为O(n)

    // 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)

    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)

    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)

    // 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
    }
    

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

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 2 3


1
@Simon,您能找出哪一部分不正确吗?
zangw

感谢您的询问。我误读了代码。我删除了我的评论。抱歉!
西蒙

74

时间复杂度示例

1-基本操作(算术,比较,访问数组的元素,分配):运行时间始终为常数O(1)

范例:

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

2-如果则else语句:仅从两个或多个可能的语句中获取最大运行时间。

例:

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-循环(重复执行):此语句的运行时间是循环数乘以该循环内的操作数。

例:

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)。

例:

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;

普通运行时间

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

  1. O(1)–恒定时间恒定时间表示运行时间是恒定的,不受输入大小的影响

  2. O(n)–线性时间当算法接受n个输入大小时,它也会执行n次操作。

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

  4. O(n log n)–线性运算时间该运行时间通常在“分而治之算法”中找到,该算法将问题递归地划分为子问题,然后在n时间内合并它们。示例:合并排序算法。

  5. O(n 2)–二次看时气泡排序算法!

  6. O(n 3)–立方时间它具有与O(n 2)相同的原理。

  7. O(2 n)–指数时间随着输入变大,这非常慢,如果n = 1000.000,则T(n)为21000.000。蛮力算法具有此运行时间。

  8. O(n!)–阶乘时间最慢!示例:旅行推销员问题(TSP)

摘自本文。很好的解释应该给读。


在第二个示例中,您编写的visitors = visitors + 11 + 1 = 2。您能告诉我为什么这么做吗?
萨吉布·阿查里亚2015年

3
@Sajib Acharya从右到左看。第一步:计算visitors + 1 第二步:将第一步的值赋给visitors So,上面的表达式由两个语句组成;第一步+第二步=> 1 + 1 = 2
Bozidar Sikanjic '16

@nbro为什么是1 + 1?age = read(x) // (1+1) = 2
Humty '17

@BozidarSikanjic为什么会是1 + 1?age = read(x) // (1+1) = 2
Humty '17

1
@Humty检查此答案的开头:read(x) // O(1) a = 10; // O(1)首先是函数调用=> O(1)//////第二个是赋值,如nbro所说,但是10是常数,所以第二个是=> O(1)...
Bozidar Sikanjic

41

在分析代码时,必须逐行分析代码,计算每个操作/识别时间的复杂性,最后,必须对其求和以得到完整的图像。

例如,您可以有一个具有线性复杂度的简单循环,但是在同一程序的后面,您可以有一个具有三次复杂度的三重循环,因此您的程序将具有三次复杂度。功能增长的顺序就在这里发挥作用。

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

  • 恒定时间具有增长顺序1,例如:a = b + c

  • 对数时间具有增长顺序LogN,通常在将某物分成两半(二进制搜索,树,偶数循环)或以相同方式相乘时发生。

  • N例如,线性的增长顺序是

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

  • 三次,增长顺序N^3,经典示例是一个三重循环,您可以在其中检查所有三胞胎:

    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通常发生在您进行详尽搜索时,例如检查某些集合的子集。


如果是这种情况,那么复杂度如何?对于(int i = 0; i <N; i ++)for(int j = i + 1; j <N; j ++)for(int k = j + 1; k <N; k ++)x = x + 2
3156040

35

宽松地说,时间复杂度是一种总结算法的操作数或运行时间如何随输入大小增加而增长的方式。

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

上)

参加聚会时,您必须与所有人握手(对每个项目进行操作)。随着参加者人数的N增加,您与所有人握手所需的时间/工作也随之增加O(N)

为什么O(N)cN呢?

与人握手所需的时间有所不同。您可以将其取平均值并将其捕获为常数c。但基本操作在这里---握手和大家---将永远是成正比的O(N),不管是什么c了。在辩论是否应该参加鸡尾酒会时,我们通常比起会议的详细细节更感兴趣,因为我们必须与所有人见面。

O(N ^ 2)

鸡尾酒会的主持人希望您玩一个愚蠢的游戏,让所有人见面。因此,您必须与N-1其他人见面,并且因为下一个人已经与您相遇N-2,所以他们必须与其他人见面,依此类推。这一系列的总和是x^2/2+x/2。随着与会者人数的增加,该x^2术语迅速增加,因此我们将其他所有内容都删除。

O(N ^ 3)

您必须与其他所有人见面,并且在每次会议期间,您必须谈论会议室中的其他所有人。

O(1)

主持人想宣布一些事情。他们在酒杯上叮叮当当,大声说话。每个人都听到。事实证明,参加会议的人数无关紧要,该操作始终花费相同的时间。

O(对数N)

主持人已按字母顺序将所有人安排在桌子旁。丹在哪里?您认为他一定在亚当和曼迪之间(一定不在曼迪和扎克之间!)。鉴于此,他在乔治和曼迪之间吗?不。他必须在亚当和弗雷德之间,以及辛迪和弗雷德之间。依此类推...通过查看一半的集合,然后查看一半的集合,我们可以有效地定位Dan。最终,我们看O(log_2 N)个人。

O(N对数N)

您可以使用上述算法在桌旁找到坐下的地方。如果有很多人一次来到桌子上,而所有人都这样做了,那将花费O(N log N)时间。事实证明,必须对项目集合进行排序时,需要花费多长时间。

最佳/最坏情况

您到达聚会并需要找到Inigo-需要多长时间?这取决于您何时到达。如果每个人都四处乱逛,您会遇到最坏的情况:这需要O(N)时间。但是,如果每个人都坐在桌旁,那么只需要O(log N)时间。或者,您可以利用主持人的酒杯启动功能,这仅需要O(1)时间。

假设主持人不可用,我们可以说Inigo-finding算法具有的下限O(log N)和上限O(N),具体取决于您到达时的聚会状态。

太空与通讯

可以将相同的思想应用于理解算法如何使用空间或通信。

克努斯(Knuth)就前者写了一篇很好的论文,名为“歌曲的复杂性”

定理2:存在复杂度为O(1)的任意长歌曲。

证明:(由于Casey和阳光乐队)。考虑由(15)定义的Sk歌曲,

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

对于所有k。


您已将其钉牢,现在,每当我去参加鸡尾酒会时,我都会下意识地尝试查找任何有趣事件的时间复杂性。感谢您提供这样一个幽默的例子。
Sabunkar Tejas Sahailesh

5

我知道这个问题可以追溯到过去,这里有一些很好的答案,尽管如此,我还是想与那些精通数学的人分享一下,他们将在本文中绊倒。该主定理是另一种有用的东西学习复杂的时候就知道了。我没有在其他答案中看到它。


2

O(n)是大的O符号,用于写算法的时间复杂度。当您将算法中的执行次数加起来时,您将得到一个表达式,其结果类似于2N + 2,在该表达式中N是主要术语(该术语会增加或减小其值对表达式的影响最大)。现在O(N)是时间复杂度,而N是主导项。例

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)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.