浅谈时间复杂度- 算法衡量标准Big O

写在前面:

今天有一场考试,考到了Big-O的知识点,考到了一道原题,原题的答案我记住了,但实际题目有一些改动导致答案有所改动,为此作者决定重新整理一下复杂度相关知识点

Efficiency and Complexity。

我觉得的学习Big-O之前有必要先了解一下以下这些知识,其中大部分翻译自我们老师的课件,也有一部分自己理解加入,如果有专业名词翻译错误欢迎指正!

  • 时间复杂度与空间复杂度

在写程序的时候,我们通常需要判断某一个算法或者程序是否可以完成某一项任务。拿12306的铁路订票系统举例,如果完成一次订票需要半个小时或者更久,这是很难被用户认可或者接受的。所以保证等待时间的合理是非常必要的(当然越短越好)。我们将一个算法的时间复杂度作为一个指标,而最终执行时间取决于数据结构的大小。

另一个效率指标就是一个程序需要多少空间去来执行特定任务,但是这个指标在现代的计算机我们考虑的很少。我们一般可以认为空间复杂度取决于数据结构的大小。

我们会发现,作为一个数据存储设备,哈希表有一个很好的时间复杂度为,但代价是会比其它算法使用更多的内存。所以两个复杂度通常是由算法/程序员决定如何最好地为程序平衡取舍

  • 最差和平均复杂度(worst versus average complexity)  

另一个很重要的指标就是平均复杂度,有的情况下,平均复杂度要比最差复杂度要重要很多。比如一个日常的软件,这里拿我们经常使用的操作系统Windows来举例子,即使经常死机(此时是最差的情况),只要保证在大部分情况下可以正常的运行就可以,也就是说可以为了保证平均的复杂度比保证最坏情况运行更加重要。而在另外一些情况下,比如录音软件,为了采集连续数据,需要考虑时效性,它完全不能接受软件等待时间太长,这样采集数据就不会连续,录音就会不完整。

而这些也是通过具体的例子来进行权衡的。

  • 具体的性能指标

大多数情况,我们感兴趣的是时间复杂度(time complexity)也就是经常提到的Big-O,因此我们需要了解怎么计算它。一个人可能写了一个程序并且运行,并且看看这个程序运行了多长时间,但是这个程序可能会发生一些意想不到的错误,尤其是对于大型的程序,可能每次运行由于不同的条件会发生很大的变化,并且同样程序会因为处理器,或者数据量,读写速度等很多条件下不同,因此时间并不能很好的衡量一个程序或者算法的性能。而复杂度是最好的方式来衡量一个算法/程序,我们可以用伪代码来计算复杂度,由于伪代码很接近我们写程序,并且不需要花费大部分时间来完成程序(我们的任务不就是来计算时间复杂度吗,所以不需要花费太多时间来计算时间复杂度)

 

 

 

Big O notation

Big O notation是一种描述述函数渐进行为的理论,又被称作Landau’s symbol,其实这部分是高数中的概念。用于表述一个函数增长的多快或者减少的多快。(重点描述加速度),在计算机领域,Big O notation常常用来表达算法时间复杂度和性能的术语,一个算法在执行过程中需要的最大时间或者最大空间。

那么为什么要用O来表示呢?
大O符号是由德国数论学家保罗·巴赫曼(Paul Bachmann)在其1892年的著作《解析数论》(Analytische Zahlentheorie)首先引入的。而这个记号则是在另一位德国数论学家艾德蒙·朗道(Edmund Landau)的著作中才推广的,因此它有时又称为朗道符号(Landau symbol)。代表“order of …”(……阶)的大O,最初是一个大写的希腊字母’Θ’(Omicron),现今用的是英文大写字母’O’,但从来不是阿拉伯数字’0’。

 

最常见的几种复杂度类型(升序排列)

O(1):常数,结果是相同的,输入值不影响时间(空间),

O(loglogn), 

O(logn):对数,经典算法:二叉搜索树(AVL数)查找算法,

O(n),  线性,表示随着输入的增加,所需的执行时间(空间)也线性增加,

O(nlogn),

O(n^2), 

O(n^3),  

O(2^n )

….

我们可以观察不同n值下面具体复杂度的值。

显示在坐标中会更加明显

 

那么问题是:如何计算呢?

时间复杂度是总运算次数表达式中受n的变化影响最大的那一项(不含系数)

例如:

O(n^2 + 4n + 1) 受 n^2影响最大,于是二次方程我们记为O(n^2)

同理:

O(2n^3 + n^2 + 1) = O(n^3)(此处=不知是否妥当,欢迎网友指正)

O(n+4logn) = O(n)

 

这里我们引入T(n)概念,时间复杂度的表示法T(n)=O(f(n)) 其中f(n)一般是算法中频度最大的语句频度

 

例如:

 

1.数组遍历

for(int i = 0; i < a.length; i++){
    system.out.print(a[i]+" ")
}

很明显遍历数组元素个数n次 也就是说f(n) = n,

那么T(n) = O(n)

 

2.查看数组中是否有重复元素

1 for (int i=0; i<n; i++){ 
2     for (int j=i+1; j<n; j++){ 
3         if (a[i]==a[j]) {
4             eturn true; 
5         } 
6     }
7 } 
8 return false; 

分析:

对于第一个for循环,0<i<n,最坏的情况下有n次;分别是n=0,1,2…i…n-1.

对于第二个for循环,i+1<j<n, 最坏的情况下有 n-i-1次,将0<i<n以此代入

即 n-1, n-2, n-3….n-i-1….0

相加便是n(n-1)/2次运算 即f(n) = n(n-1)/2

那么T(n) = O(n(n-1)/2)也就是T(n) = O(n^2)

以上是完整的分析方式,但是对于算法来说,我们通常不会如此细致的去分析,因为本来复杂度都是取的最坏的情况 因此我们只需要估算。

 

3.

i = 1while(i <= n){
    i = i*2
}

我们可以看到 执行f(n)次

也就是2^f(n) <= n

f(n) = log2 n

T(n) = O(f(n)) = O(logn)   (这里通常会把log2 n记为 logn)

 

声明:

本文70%原创, 30%摘抄整理翻译

引用:

http://blog.csdn.net/firefly_2002/article/details/8008987

http://blog.csdn.net/iluna/article/details/4159485

http://www.ehcoo.com/complexity.html

    原文作者:chuanheliu
    原文地址: https://www.cnblogs.com/chuanheliu/p/6389037.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞