时间复杂度 & 空间复杂度

296 阅读1分钟

时间复杂度

时间复杂度,是用来描述算法运行时间的函数,使用大O符号表述。值得注意的是,大O符号表示法并不是用来代表算法执行的真实消耗,它是用来表示代码执行时的空间或时间消耗的增长变化趋势的

T(n) = O(f(n))

T(n):表示时间复杂度函数 f(n):表示每行代码执行的次数之和 O:表示其正比例关系

常用的时间复杂度量级:

1、常数阶

当算法的T(n)不受输入变量n的影响,则称其为常数时间。

T(n) = O(1)

示例代码:

let i = 1
var j = 2

函数图像: b53b89d5bf7744ce2cae7ea003f379a8.png

2、对数阶

对数阶算法的时间消耗,会随着输入量n的增加而变小。 由于log_a(N)log_b(N)只有一个常数因子不同(通过对数的换底公式可得),所以对数阶的时间复杂度可以用大O法表示为:

T(n) = O(\log n)

示例代码:

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

函数图像: b34f697bde7d01bc68c7e76f448a003e.png

3、线性阶

当对于足够大的输入n,算法的运行时间增加的大小与输入成线性关系,则这个算法具有线性阶的时间复杂度。

T(n) = O(n)

示例代码:

var j = 0
for i in 0...n {
    j = i
    j += 1
}

示例代码中的代码一共执行了1+2n次,此时f(n)=1+2n。当n足够大时,乘数和加数常量对函数的结果没有影响,所以用大O表示法时可以省略,即记做O(n)

函数图像: 769336366622002f1a5b96bb73818435.png

4、平方阶

只要在O(n)的代码中再嵌套一个相同的量级的函数,就可以得到平方阶的时间复杂度函数。同理还可得,立方阶O(n^3),k次方阶O(n^k)

T(n) = O(n^2)

示例代码:

for i in 0...n {
    sum += i
    for j in 0...n {
        sum += j
    }
}

函数图像: 4314857998c275984aa550ad17cfcea0.png

5、指数阶

T(n)对某些常量k是由O(2)所界定,则算法被称为指数时间。

T(n) = O(2^n)

示例代码:

func fibonacci(_ n: Int) -> Int{
    if n <= 1 {
        return 1
    }else{
        return fibonacci(n-1) + fibonacci(n-2)
    }
}

函数图像: be249a6ad57e9e499dc2b97f7af77c5f.png

各量级的比较

84a84c17d9a202f792ce165879b40baa.png 从时间复杂度的函数曲线可以得出,各个量级的算法所需的执行时间,从小到大分别是:

O(1) < O(\log n) < O(n) < O(n^2) < O(2^n)

空间复杂度

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度,它同样反映的是一个趋势,我们用S(n)来定义,记做

S(n)=O(g(n))

常用的空间复杂度量级:

1、空间复杂度 O(1)

当一个算法在运行过程中所占临时空间大小,不随被处理数据量n的大小而改变时,可表示为O(1)。

示例代码:

let i = 0
var j = 1

代码中i,j所分配的空间不会随着处理数据量的变化而改变,因此它的空间复杂度 S(n) = O(1)

2、空间复杂度 O(n)

传统递归算法,拥有O(n)的空间复杂度,因为每次递归都要存储返回信息。

示例代码:

func fibonacci(_ n: Int) -> Int{
    if n <= 1 {
        return 1
    }else{
        return fibonacci(n-1) + fibonacci(n-2)
    }
}

总结

空间复杂度描述的算法执行时所需要的额外空间,它不会超过其时间复杂度,时间复杂度为空间复杂度定了一个上界,这也是我们平时不怎么关注空间复杂度的原因之一。