【数据结构与算法】之复杂度分析---第一篇

博主秋招提前批已拿百度、字节跳动、拼多多、顺丰等公司的offer,可加微信:pcwl_Java 一起交流秋招面试经验,可获得博主的秋招简历和复习笔记。 

一、首先明确两个问题:

1、为什么需要对算法进行复杂度分析?

实际上一个算法执行所耗费的时间和空间是无法从理论上准确算出来的,必须在计算机上实际运行才知道,但是我们不可能对每个算法都先在计算机上运行一遍,再决定采用其中效率最高的那个。所以我们就需要从理论上分析出每种算法的复杂度,从而去预测其在运行的过程中所需要耗费的资源。这两种方式正好分别对应着通常度量一个程序执行时间的两种方法:事后统计法事前分析估算法。这两种方法从名字上就可以看出其含义,就不做过多的解释。

对算法进行预测分析包括以下方面:

(1)预测算法所需的资源

            《1》计算时间(CPU消耗)

            《2》内存空间(RAM消耗)

            《3》通信时间(带宽消耗)

(2)预测算法的运行时间

              在输入规模一定时,所执行的基本操作的总数量,即算法的时间复杂度

2、如何衡量算法的复杂度?

衡量一个算法的好坏,我们需要给出一些评定的指标,首先我们能够想到的就是:时间和内存,其实还可以从其他方面去衡量,比如:访问磁盘的次数、指令的总数量等等。但是我们一般只需要关注时间和内存就可以了。

总结以上两个问题就是:同一问题可以用不同算法解决,而一个算法的质量优劣将影响到算法乃至程序的效率。算法分析的目的在于选择合适算法和改进算法。一个算法的评价主要从时间复杂度空间复杂度来考虑。

下面正式进行算法复杂度的讲解:

二、复杂度的基本概念

首先来看下维基百科中对算法复杂度的定义:算法复杂度是指算法在编写成可执行程序后,运行时所需要的资源,资源包括时间资源和内存资源。所以复杂度分为:时间复杂度和空间复杂度。

1、时间复杂度

1.1  基本概念

算法的时间复杂度是一个函数,它定量的描述了该算法的运行时间。这是一个关于代表算法输入值的字符串的长度的函数。时间复杂度通常用大O符号表示,不包括这个函数的低阶项和首项系数。使用大O表示法时,时间复杂度可被称为是渐进的,它考察当输入值大小趋近于无穷的时的情况,记作O(f(n))。

需要注意的是时间复杂度的全称是:渐进时间复杂度,即表示的算法执行时间与数据规模之间的增长关系。

除了时间复杂度的基本概念外,还需要明确其他几个有关时间复杂度的概念:

复杂度类型概念
最好情况时间复杂度在理想情况下,执行该算法的时间复杂度。比如需要在一个数组的末尾插入一个数
最坏情况复杂度在最糟糕的情况下,执行该算法的时间复杂度。比如要在一个数组的开头位置插入一个数
平均情况复杂度要查找的数A在数组中的位置有n+1种情况(包含不在数组中的1种情况)。把每周情况下,需要遍历的元素的个数累加起来,然后再除以(n+1),就可以得到在此数组中查找一个元素的平均时间复杂度
均摊时间复杂度均摊时间复杂度需要考虑每种情况发生的概率,也就是数学中的期望值。

ps:想更多的了解均摊时间复杂度可以阅读:均摊时间复杂度分析

1.2  求解时间复杂度的具体步骤

(1)找出算法的基本语句:即执行次数最多的语句,通常为内循环体;

(2)计算基本语句执行次数的数量级,只保留最高数量级即可,其他省略;

(3)用大O表示其时间复杂度,即为基本语句执行的数量级

1.3 时间复杂度计算的几个法则

(1)加法法则:总复杂度等于量级最大的那段代码的复杂度

(2)乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积

(3)求和法则:T1(m) + T2(n) = O(f(m)) + g(n))

(4)求积法则:T1(m) * T2(n) = O(f(m) * f(n)),和乘法法则一致

1.4 常见的时间复杂度示例

常见算法的时间复杂度由:常数阶O(1),对数阶O({\color{Red} }\log _2{n}),线性阶O(n),线性对数阶(n\log _2{n}),平方阶(n^{2}),立方阶(n^{3}),k次方阶(n^{k})、指数阶(2^{n})以及阶乘阶(n!)。它们的时间复杂度由小到大依次为:

对于上面这些复杂度量级,我们可以粗略的分为两类:多项式量级(Polynomial)非多项式量级(Non-Deterministic Polynomial)。其中把O(2^{n})和O(n!)称为非多项式量级,其他的称为多项式量级。一般认为多项式量级的算法才是有效的。

(1)常数阶O(1)

// 计算两个变量的和
int a = 10;
int b = 20;
int sum = a + b;

(2)对数阶O({\color{Red} }\log _2{n})

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

对数阶O({\color{Red} }\log _3{n})

int i = 1;
while(i <= n){
    i = i * 3;
}

ps:如果对数函数不是很熟悉,可以阅读:对数函数的概念

上面这段代码即求:2^{i} = n,所以即i = {\color{Red} }\log _2{n}3^{i} = n即i = {\color{Red} }\log _3{n}

(3)线性阶(O(n))

int sum = 0;
for(int i = 0; i <= n; i++){
    sum++;
}

(4)线性对数阶(O(n\log _2{n}))

for(int j = 0; j < n; j++){
    int i = 1;
    while(i <= n){
       i = i * n;
    }
}

其实线性对数阶就是一个对数阶和一个线性阶的之间的乘法。

(5)平方阶(O(n^{2}))

int sum = 0;
for(int i = 0; i < n; i++){
    for(int j = 0; j < n; j++){
        sum++;
    }
}

一般复杂度为平方阶的都是有两层循环,所以立方阶的最好例子就是嵌套三层循环的程序,K次方阶的依次类推...

有关指数阶(2^{n})和阶乘阶(n!)的算法都比较复杂,这里就不再进行举例分析,感兴趣的可以参考:算法复杂度分析

2、空间复杂度

空间复杂度的全称是:渐进空间复杂度,即表示算法的存储空间与数据规模之间的增长关系,也是问题规模为n的函数,分析过程和时间复杂度类似。

其类似于时间复杂度的分析,主要用于分析该算法所耗费的存储空间。一个算法在计算机存储上所占用的空间主要包括三个方面:算法本身所占用的存储空间,算法的输入输出数据所占用的存储空间以及算法在运行过程中临时占用的空间。

算法的本身身所占用的存储空间和算法的书写长度成正比,这就要求我们尽量写出简短的算法实现程序;

算法的输入输出数据所占用的存储空间是由算法解决的问题决定的,是通过参数表由调用函数传递而来的,它不随算法的不同而改变;

算法在运行过程中临时占用的存储空间随算法的不同而改变,有的算法只需要占用少量的临时单元,而且不随问题的规模大小而改变,称这种算法为:“就地算法”,是节省存储的算法。但是有的算法需要占用的临时工作单元数与解决问题的规模n有关,它随n的增大而增大,当n较大时,将会占用较多的存储单元,例如:归并排序和快速排序。

我们平时常见的空间复杂度是:O(1)、O(n)、 O(n^{2}) ,它们之上的高数量级空间复杂度几乎用不到。

下面给出一个分析空间复杂度的简单例子

public void print(int n){
    int[] a = new int[n];
    int i = 0;

    for(i = 0; i < n; i++){
        a[i] = i * i;
    }
    
    for(i = n - 1; i >= 0; --i){
        System.out.println(a[i]);
    }
}

空间复杂度分析:第2行代码中,申请了一个大小为n的int类型数组,其空间复杂度为O(n),第3行申请了一个存储空间用来存储变量i,它的空间复杂度是常量阶的O(1),所以该程序的空间复杂度为O(n)。


参考及推荐:

1、算法复杂度分析

2、常用数据结构及其复杂度    推荐阅读

3、算法的时间复杂度和空间复杂度总结

由于本人水平有限,即便参考了很多大佬的文章,难免还是会出现错误的地方,如果有人发现,还请指正!谢谢!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值