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

Posted

技术标签:

【中文标题】如何找到算法的时间复杂度【英文标题】:How to find time complexity of an algorithm? 【发布时间】:2012-06-17 10:08:04 【问题描述】:

问题

如何求算法的时间复杂度?

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

我浏览过this、this 和许多其他链接

但我无法找到关于如何计算时间复杂度的清晰直接的解释。

我知道什么?

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

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而不是声明。

i 这将被执行 N+1

i++ ; 这将被执行 N

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

1+(N+1)+N = 2N+2

注意:这仍然可能是错误的,因为我对计算时间复杂度的理解没有信心

我想知道什么?

好的,所以这些小的基本计算我想我知道,但在大多数情况下,我已经看到时间复杂度为

O(N), O(n2), O(log n), O(n!)....还有很多other,

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

【问题讨论】:

对那些感兴趣的人的奖励:Big O 备忘单bigocheatsheet.com 查看此博客:mohalgorithmsorbit.blogspot.com。它涵盖了递归和(尤其是)迭代算法。 为什么是 Console.Write('Hello World !');不是机器指令? @Chetan 如果你的意思是在计算复杂度时应该考虑Console.Write,那是真的,但在这种情况下也有点不相关,因为这只改变了一个常量因子,big-O 忽略了 (查看答案),所以最终结果仍然是 O(N) 的复杂度。 相关:What is a plain English explanation of "Big O" notation? 和 Is there a system behind the magic of algorithm analysis? 【参考方案1】:

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

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

计算时间复杂度的最常用指标是大 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个对数循环(迭代或递归)组成,因此该算法是线性和对数的结合。

一般来说,对一维中的每个项目做某事是线性的,对二维中的每个项目做某事是二次的,将工作区域分成两半是对数的。还有其他大 O 度量,例如三次、指数和平方根,但它们并不常见。大 O 表示法被描述为 O ( &lt;type&gt; ),其中 &lt;type&gt; 是度量。快速排序算法将被描述为O ( N * log ( N ) )

请注意,所有这些都没有考虑到最佳、平均和最差情况的衡量标准。每个都有自己的大 O 符号。另请注意,这是一个非常简单的解释。 Big O 是最常见的,但也比我展示的更复杂。还有其他符号,例如大 omega、小 o 和大 theta。您可能不会在算法分析课程之外遇到它们。 ;)

【讨论】:

quicksort 算法在最坏情况下的运行时间为 N^2,尽管这种行为很少见。 IIRC、little o 和 big omega 用于最佳和平均情况复杂度(大 O 是最坏情况),因此“最佳、平均和最坏情况测量。每个都有自己的 Big O符号。”将是不正确的。还有更多具有更具体含义的符号,而 CS 并不总是使用最合适的符号。我是通过Landau symbols btw 的名字来学习所有这些的。 +1 反正 b/c 最佳答案。 @hiergiltdiestfu Big-O、Big-Omega 等可以应用于算法的任何最佳、平均或最差情况运行时间。 How do O and Ω relate to worst and best case? 另外,如果有人正在寻找如何为任何方法计算大 O:***.com/a/60354355/4260691 最好的解释之一。【参考方案2】:

取自这里 - Introduction to Time Complexity of an Algorithm

1。简介

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

2。大 O 符号

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

例如,如果一个算法在所有大小为 n 的输入上所需的时间最多为 5n3 + 3n,则渐近时间复杂度为 O(n3 )。稍后会详细介绍。

还有几个例子:

1 = O(n) n = O(n2) 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) 对数时间:

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

示例:Binary Search

回想一下“二十题”游戏——任务是在一个区间内猜测一个隐藏数字的值。每次您进行猜测时,系统都会告诉您您的猜测是太高还是太低。二十个问题游戏意味着使用您的猜测数将间隔大小减半的策略。这是被称为二分搜索的一般问题解决方法的一个示例

6。 O(n2) 二次时间

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

例子:

Bubble Sort Selection Sort Insertion Sort

7。一些有用的链接

Big-O Misconceptions Determining The Complexity Of Algorithm Big O Cheat Sheet

【讨论】:

注意:第一个链接坏了。 O(n2) 应该写成 O(n^2) 以避免混淆。【参考方案3】:

当你在分析代码时,你必须逐行分析它,计算每个操作/识别时间复杂度,最后,你必须将其相加才能得到全貌。

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

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

常数时间有一个增长顺序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,通常发生在您进行详尽搜索时,例如检查某些集合的子集。

【讨论】:

如果是这种情况,复杂性会是多少? for (int i = 0; i 【参考方案4】:

如何计算算法的时间复杂度

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

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

我们为什么要删除这两个2s?

我们对算法的性能感兴趣,因为 N 变大了。

考虑两个术语 2N 和 2。

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

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

因此,我们删除了大 N 的所有项,但最大的项除外。

所以,现在我们已经从2N + 2 变成了2N

传统上,我们只对性能感兴趣取决于常数因素

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

所以2N 变成了N

【讨论】:

嘿,感谢您让我知道“为什么 O(2N+2) 到 O(N)”得到很好的解释,但这只是更大问题的一部分,我希望有人指出一些指向隐藏资源的链接,或者一般来说,我想知道你最终会遇到 O(N)、O(n2)、O(log n)、O(n!) 等时间复杂度。我知道我可能会问很多,但我仍然可以尝试:) 嗯,括号中的复杂性就是算法需要多长时间,使用我解释过的方法进行了简化。我们通过简单地将算法将执行的机器指令的数量相加来计算算法需要多长时间。正如我所解释的,我们可以通过只查看最繁忙的循环并除以常数因子来简化。 对未来的读者来说,给出一个答案中的例子会有很大帮助。只是交出一个我必须注册的链接,当我只想浏览一些解释清楚的文字时,真的对我没有帮助。 如果您想深入了解 DS 和时间复杂度,我建议您观看 Naveen Garg 博士(印度理工学院德里教授)的视频。请查看链接。nptel.ac.in/courses/106102064 (cont.) 这个层次结构的高度大约为 log N。至于 O(N!) 我的类比不太可能削减它,但排列是按这个顺序排列的——它令人望而却步陡峭,比任何多项式或指数都要陡峭。正好有10个!六周内的秒数,但宇宙还不到 20!秒旧。【参考方案5】:

我知道这个问题可以追溯到很久以前,这里有一些很好的答案,但我想与那些在这篇文章中遇到问题的有数学头脑的人分享一点。在研究复杂性时,Master theorem 是另一个有用的知识。我没有在其他答案中看到它。

【讨论】:

【参考方案6】:

虽然这个问题有一些很好的答案。我想在这里用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

【讨论】:

@Simon,你能找出哪个部分不正确吗? 感谢您的提问。我误读了代码。我删除了我的评论。对不起! 在分析中应该是 O(n ^ 2) 。【参考方案7】:

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

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

O(N)

当你到达派对时,你必须和每个人握手(对每个项目进行操作)。随着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(log N)

主持人已按字母顺序将每个人都安排在餐桌上。丹在哪里?你推断他一定介于亚当和曼迪之间(当然不是在曼迪和扎克之间!)。鉴于此,他在乔治和曼迪之间吗?不,他必须在亚当和弗雷德之间,在辛迪和弗雷德之间。依此类推……我们可以通过查看一半的集合然后查看该集合的一半来有效地定位 Dan。最终,我们会查看 O(log_2 N) 个个体。

O(N log N)

您可以使用上面的算法找到在桌边坐下的位置。如果有大量的人来到餐桌旁,一次一个,并且所有人都这样做,那将花费 O(N log N) 时间。事实证明,当必须进行比较时,对任何项目集合进行排序需要多长时间。

最佳/最坏情况

你到了派对,需要找到 Inigo - 需要多长时间?这取决于你什么时候到达。如果每个人都在你身边,你已经遇到了最坏的情况:这将需要O(N) 时间。但是,如果每个人都坐在桌旁,则只需要O(log N) 时间。或者,也许您可​​以利用主持人的酒杯喊叫能力,并且只需要O(1) 时间。

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

空间与交流

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

Knuth 写了一篇关于前者的好论文,题为@9​​87654321@。

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

证明:(由于凯西和阳光乐队)。考虑由 (15) 定义的歌曲 Sk,但带有

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

对于所有 k。

【讨论】:

你成功了,现在每当我去参加鸡尾酒会时,我都会下意识地尝试寻找任何有趣事件的时间复杂度。谢谢你这么幽默的例子。【参考方案8】:

时间复杂度与示例

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

例子:

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

2 - If then 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 - 循环(for、while、repeat):此语句的运行时间是循环次数乘以该循环内的操作数。

例子:

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 - 嵌套循环(looping inside looping):由于主循环内至少有一个循环,因此该语句的运行时间使用了 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;

常用运行时间

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

    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)

取自this article。解释得很好,应该读一读。

【讨论】:

在你的第二个例子中,你写了visitors = visitors + 1 is 1 + 1 = 2。你能解释一下你为什么这样做吗? @Sajib Acharya 从右到左看。第一步:计算visitors + 1 第二步:将第一步中的值赋值给visitors 所以,上面的表达式由两个语句组成;第一步+第二步=>1+1=2 @nbro 为什么在age = read(x) // (1+1) = 2中是1+1 @BozidarSikanjic 为什么在 age = read(x) // (1+1) = 2 中是 1+1 @Humty 检查这个答案的开头:read(x) // O(1) a = 10; // O(1) 第一个是函数调用 => O(1) ///// 第二个是赋值,正如 nbro 所说,但是 10 是常量,所以第二个是 => O(1)...【参考方案9】:

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)

【讨论】:

以上是关于如何找到算法的时间复杂度的主要内容,如果未能解决你的问题,请参考以下文章

算法异或 偶数数组中找到一个唯一奇数

如何找到图像处理算法的计算复杂度

该算法的复杂度函数

从最大连续和问题看算法的时间复杂度

素数算法的复杂度

我的算法的复杂性[重复]