一、复杂度的概念
算法运行时需要耗费时间资源和空间(内存)资源,衡量算法的好坏一般从时间复杂度和空间复杂度这两个维度进行。时间复杂度 主要是衡量算法运行的快慢 ,空间复杂度 主要是衡量算法运行所需的额外空间。
二、时间复杂度
定义:在计算机科学中,算法的时间复杂度是一个函数式 T (N) ,它定量描述了该算法的运行时间 。时间复杂度用于衡量程序的时间效率。
那为什么不能不直接计算程序运行时间呢?
(1)程序运行时间与编译的环境和运行机器的配置有关。例如同一算法程序,用老编译器和新编译器分别编译,在同样的机器下运行的时间是不同的。
(2)同一算法程序,在老低配置的机器上和新高配置的机器上运行时间是不同的。
(3)运行时间只能在程序写好后测试,不能在编写程序前通过理论思想计算评估。
补充:程序的运行时间,可以通过在程序首尾分别调用clock()函数,再作差来显示。
函数原型是:clock_t clock ( void );
返回程序运行的时间,单位是毫秒(ms)。
那么算法的时间复杂度是一个函数式 T (N) 到底是什么呢?
这个 T (N) 函数式计算了程序的执行次数 。由 c 语言编译链接相关知识,我们知道算法程序被编译后生成二进制指令,程序运行的实质就是 cpu 执行这些编译好的指令。那么我们通过程序代码或者理论思想计算出程序的执行次数的函数式 T (N),假设每句指令执行时间基本一样 (实际中有差别,但是微乎其微),那么执行次数和运行时间就是等比正相关 ,这样计算也就脱离了具体的编译运行环境。执行次数就可以代表程序时间效率的优劣。比如:解决一个问题的算法 a 程序的 T (N) = N,算法 b 程序的 T (N) = N^2,那么算法 a 的效率一定优于算法 b。
实际中我们计算时间复杂度时,计算的也不是程序的精确的执行次数 ,精确执行次数计算起来还是很麻烦的 (不同的一句程序代码,编译出的指令条数都是不一样的),计算出精确的执行次数意义也不大,我们计算时间复杂度只是想比较算法程序的增长量级 ,也就是当 N 不断变大时 T (N) 的差别,我们知道当 N 不断变大时常数和低阶项对结果的影响很小,(这里其实与高数中当x趋于无穷时,求极限类似,我们只需要关注最高次幂的那一项),所以我们只需要计算程序能代表增长量级的大概执行次数,复杂度的表示通常使用大 O 的渐进表示法。
1、大O的渐进表示法
大 O 符号(Big O notation)是用于描述函数渐近行为的数学符号。
推导大 O 阶规则:
(1)在时间复杂度函数式 T (N) 中,只保留最高阶项,去掉低阶项。因为当 N 不断变大时,低阶项对结果影响越来越小,当 N 无穷大时,可以忽略不计。
(2)如果最高阶项存在且不是 1 ,则去除这个项目的常数系数。因为当 N 不断变大时,这个系数对结果影响越来越小,当 N 无穷大时,可以忽略不计。
(3)在 T (N) 中如果没有 N 相关的项目,只有常数项 ,用常数 1 取代所有加法常数。
2、计算时间复杂度举例
(1)
(2)
(3)
(4)
**总结:**通过上述内容可以发现,有些算法的时间复杂度存在最好、平均和最坏情况。
1)最坏情况 :任意输入规模的最大运行次数(上界)
2)平均情况:任意输入规模的期望运行次数
3)最好情况 :任意输入规模的最小运行次数(下界)
大 O 的渐进表示法在实际中一般情况关注的是算法的上界,也就是最坏运行情况。
(5)
(6)
当 n 接近无穷大时,底数的大小对结果影响不大。因此,一般情况下不管底数是多少都可以省略不写,即可以表示为 log n。
不同书籍的表示方式不同,以上写法差别不大,建议使用 log n。
(7)
三、空间复杂度
1、空间复杂度介绍
空间复杂度 是一个数学表达式,用于衡量算法在运行过程中需要额外临时开辟的空间。它并非程序占用的字节数,而是计算变量的个数,因为通常情况下每个对象的大小的差异不大。
空间复杂度计算规则与时间复杂度类似,也使用大 O 渐进表示法。
需要注意的是,函数运行时所需的栈空间(存储参数、局部变量、寄存器信息等)在编译期间已经确定,因此空间复杂度主要通过函数在运行时显示申请的额外空间来确定。
2、计算空间复杂度举例
(1)
(2)