• 如何衡量算法优越性?
  • 发布于 2个月前
  • 274 热度
    0 评论
  • LoveC
  • 1 粉丝 35 篇博客
  •   
为何需要算法?
我们程序员每天都在和程序打交道,而我们写的程序都是通过代码表示出来的,图灵奖得主Niklaus Wirth说过一句话Programs = Algorithm + Data Structures,足以证明算法在程序中的重要性,确实程序中的很多复杂的问题,都可以使用算法解决。

大部分算法都是应用在数据结构基础之上的,如果不了解一些常见的数据结构原理,怎么能学习好算法呢?其实大部分考查算法就是为了看程序员能不能用不同数据结构去解决问题。数据结构可以把我们现实生活中的东西通过数据结构表示出来,这样我们就可以通过算法组织管理这些数据了,从而解决问题。

网络上还流行一些公式:
程序 = 算法 + 数据结构
软件 = 程序 + 软件工程

软件公司 = 软件 + 商业模式


作为一个普通业务仔,如果不是专门做算法工程师或者说是搞数学相关的,其实掌握一些常用的数据结构和一些算法就可以了,其实大部分我们开发的时候还是靠着Google搜索相关资料。数据结构就像内功一样,先学好基础招式,练好内功,然后触类旁通,还可以在别人的基础上做改进。

如何衡量算法优越性?
其实我们在买电脑的时候都会看看电脑几个核心或者多少GB的内存啊,有没有想过为什么要查看这些参数,或者是市面上那么多电脑,为什么配置要不一样,为何配置不统一呢?

如果了解计算机程序怎么工作的话,应该明白程序在运行的时候是要占用内存的,还有CPU资源的。换到程序员角度的话,也就是我们在编写程序代码的时候,是不是可以让程序开销更小,性能更好,例如我现在有10000个元素的无序数组,怎么在最短时间内完成排序呢?这就是算法和数据结构的应用要解决的问题。

衡量一个算法的标准有2个很重要的指标,时间复杂度,空间复杂度。

时间复杂度
一个算法运行过程中也看作为一段程序运行过程,而这个过程所消耗的时间那么就是这段算法生命周期。同样一个需求,安排给两个不同程序员来写的话,可能写出来的程序运行起来耗时,可能有所差距,这也就是所为时间复杂度,衡量一个算法时间复杂度,通常有一种叫大O符号表示法标准去衡量。

在大O符号表示法中,时间复杂度的公式是:T(n) = O(f(n)),其中f(n)表示每行代码执行次数之和,而O表示正比例关系,这个公式的全称是:算法的渐进时间复杂度。

可能很多人认为为什么我不直接写一段程序放在电脑上跑一下就可以计算出算法的时间复杂度了,其实这样做没有考虑到计算机硬件设备的差异化,而和使用的编程语言也有关系。可能在我电脑是多核处理器,在另外一台单片机就是另外一种结果,如果光看跑的快慢去衡量一个算法这个做法不是明智的,所以需要一套标准来衡量一个算法的好坏,这就是我们所说的时间复杂度也就是big O(n)表示法,保留阶数更高的部分就可以了。
// 堆代码 duidaima.com
// T(3000) = O(3n)
func loveYou(n int) { // n = 3000
    var count int = 1
    for count <= n {  // 3001
        count++       // 3000   
        fmt.Println("I Love You %d\n",count) // 3000
    }
    fmt.Println("I Love You More Than %d\n",n)// 3000
}
就拿上面这段程序为例,我们程序进入一个循环重复执行一段确定的逻辑代码块,问题规模是n,n=3000,那么for里面逻辑会被执行3000次,而每段逻辑在执行的时候拥有自己的时间段,加起来就可以评估出来这段程序的时间复杂度为O(3n)。

常数时间的操作,一段算法里面数据操作和样本数据的数量没有关系,每次操作的话如果是固定的时间单位内完成,那这个算法的时间复杂度为常数时间操作。

如上图我们直接操作这个数组的下标去访问元素的话,那这个时间复杂度就为O(1)。

一些总结
1.测试结果不能依据硬件环境测试数据
2.测试结果受限于输入的数据量大小
3.加法法则:代码的总体复杂度等于代码中最大复杂度那块逻辑代码复杂度。

4.乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积。


常用的复杂度级别
常量阶 O(1)
对数阶 O(logn)、O(m+n)
线性对数阶 O(nlogn)
平方阶 O(n^2^)
指数阶 O(2^n^)

阶乘阶 O(n!)


空间复杂度

众所周知程序在运行的时候都需要消耗计算机内存的,那空间复杂度就和内存有关了,下图就是为一段程序内存的布局。

还是以刚刚上面的代码片段为例子,我们这段逻辑所需要的内存空间大部分是固定,循环内部改变的是count这块内存的数据,但是count是内存数据是每次被逻辑执行覆盖的,消耗的内存空间还是那么一块。
// T(3000) = O(3n)
func loveYou(n int) { // n = 3000
    var count int = 1
    for count <= n {  // 3001
        count++       // 3000   
        fmt.Println("I Love You %d\n",count) // 3000
    }
    fmt.Println("I Love You More Than %d\n",n)// 3000
}
空间复杂度也是随着时间变化,渐进式增长的变化的,上面我写这段程序唯一操作的n变量,这个数据变化是针对n的,也就是一个常量复杂度O(1)。
下面是一张算法复杂度渐进式趋势图,里面包含各类的算法复杂度大O表示法。

小结
可以看出来一个算法的好坏,可以通过Big O表示法去衡量,如果一个算法时间复杂度越长,并且算法本身的内存空间占用越多,那就可以说明这段程序性能是不好的。当然现在很多编程语言都有基准测试工具去测试代码性能,但是这些测试工具都是依赖于程序员本身所使用的电脑硬件的,很难够精准的判断一段程序好坏,但是也不是说不能使用这些测试工具,当然这篇文章只是八股一下算法相关导论加上我个人看法,我这篇侧重点不是教你去如何评估一段程序性能,我想写的为什么要评估性能,为什么评估一个程序的好坏有什么方法...
用户评论