算法的时间复杂度到底怎么算?

10 篇文章 0 订阅
10 篇文章 0 订阅

算法的时间复杂度到底怎么算?

引言

假设计算机运行一行简单语句算作一次运算。

def func1(num):
    print("Hello, World!\n")       # 需要执行 1 次
    return 0                       # 需要执行 1 次

那么上面这个方法需要执行 2 次运算

def func2(num):
    for i in range(num):            
        print("Hello, World!\n")    # 需要执行 n 次
    return 0                        # 需要执行 1 次

这个方法需要 (n + 1) = n + 1 次运算。

为了方便描述, 我们用f(n) 代表某个方法完成处理n个数据时,需要执行运算的次数

所以上面的例子中,func1的f(n) = 2 func2的f(n) = n + 1

是不是感觉跟以前课本看见的不太一样, 以前课本都是什么O(n), O(n^2), O(1)等等, 别急,继续看

什么是算法

想计算算法的时间复杂度,首先要理解,什么是算法。

算法,是一种抽象出来的逻辑范式,是解决特定问题的方法, 是一个def(python 语言)

针对同一问题A,存在不同的解决方法(算法),
智商高的人写出来的优秀算法,在小数据集上,优秀算法解决A只需要嗖嗖嗖,3秒。 
普通智商的人抽象出来的算法,要吭哧吭哧, 4秒完成。 差距还不是很大。

这时,让我们将数据量增大1W倍,优秀算法可能需要3W秒,普通算法就需要4W秒甚至更多。 这样的差距可就不可以接受了。

那为什么计算时间会长,因为两个算法为了完成等量计算任务,执行运算的次数却不同。 普通算法可能在数值的比较和交换上明显多于优秀算法。 一旦数据量增大,消耗的时间也会显著增大

在标准配置的CPU下,相差一万秒,计算次数相差可以达到万亿的水平。

从此,不同算法有了优劣之分。 那如何评价算法的优劣呢,时间复杂度就来了。

那什么是算法的时间复杂度?

简单的来说,时间复杂度是用来概括描述算法运行时间和数据量n之间的关系。 记作T(n)

T(n)可以当做是算法执行时间的衡量,也可以是描述算法优劣和数据量n之间的一个函数。

更直白的说,时间复杂度T(n)=算完刚才那道题A需要用的时间的大概估计 = 算完刚才那道题A需要用执行的运算次数f(n)

所以最开始的两个例子,func1的f(n) = 2 = T(n) func2的f(n) = n + 1 = T(n) 是不是更顺眼了一点。
f(n) 不止代表运算次数,还可以代表算法的运行时间了(在相同配置的CPU下)

那大O记号又是什么, 为什么书里都是这些圆圈O

首先,大O记号是一种运算符,跟±*%一样,要有一个这样的预设。

大O记号完成的是约等于的功能。 想象math.floor() 或者 math.ceil(),都是约等于的功能。
具体的, O(2 * n^2 + 3) = O(n^2) O(2^n + n^2) = O(2^n) 大概就是这样估计的

量级高(阶数大)的可以直接覆盖量级低(阶数小)的,同时将倍数置成1

量级参考:
O(1) < O(n) < O(n*logn) < O(n^2) < O(n^3) < O(2^n) <O(n!)

知道了大O记号,现在给出T和O之间的关系是 T(n) = O(f(n))

为了方便描述和简便计算,大家习惯用大O记号近似表示运行时间和数据规模之间关系。

上面的两个例子,func1的T(n) = f(n) = 2 = O(2) = O(1)
func2的T(n) = f(n) = n + 1 = O(n + 1) = O(n)

就像,面试官问你这个算法时间复杂度是多少,你会说O(n) 而不是 n + 1, 不然面试官可能会把你踢出去。

O(1) 表示算法有限次执行,跟数据量无关
O(n) 表示算法运次时间跟数据量呈一次线性关系。

那么f(n) 和T(n) 到底有是什么关系

(这段不想看可以忽略,就记住T(n) = f(n) 就成)

弄清这两个的关系,又要说到刚才的大O记号了

但在此之前,首先要明确,f(n) 是描述算法运算次数与数据量之间的一个函数(比如 f(n) = n + 1)

是因为方便描述,我们才引入了大O记号,因为我们习惯说,“这个算法的时间复杂度是跟数据量呈线性关系的”,而不是说,“这个算法的时间复杂度和数据量呈现2倍加2的关系”。

接着我们定义:存在常数 c 和函数 f(n),使得当 n >= c 时 T(n) <= f(n),我们称T(n)以f(n)为界或者称T(n)受限于f(n)。 然后给出T(n) = O(f(n))

以f(n)为界,也就是无论自变量n如何取,T(n)的值永远都不会超过f(n), 我们用一个对大数的估计,来表示T(n)的增长幅度。 所以才有这个式子T(n) = O(f(n))

那如何计算f(n),算法的执行次数呢?

  1. 对于一个循环,默认循环体的执行次数为 1,循环次数为 n,则这个
    循环的时间复杂度为 O(1xn)。
def func3(num):
    for i in range(num):            # 执行次数为 num
        print("Hello, World!\n")    # 循环体执行为 1                     

此时时间复杂度为 O(num × 1),即 O(n)。

  1. 对于多个循环,默认循环体的执行次数为 1,从内向外,各个循环的循环次数分别是a, b, c…,则这个循环的时间复杂度为 O(1×a×b×c…)。
def func4(num):
    for i in range(num):                # 循环次数为 num
        for j in range(num):            # 循环次数为 num
            print("Hello, World!\n")    # 循环体执行为 1                    

此时时间复杂度为 O(num × num × 1),即 O(n^2)。

  1. 对于顺序执行的语句或者算法,总的时间复杂度等于其中最大的时间复杂度。
def func5(num):
    for i in range(num):                # 循环次数为 num
        for j in range(num):            # 循环次数为 num
            print("Hello, World!\n")    # 循环体执行为 1  第一部分时间复杂度为 O(n^2)

    for k in range(num):                # 循环次数为 num
        print("Hello, World!\n")        # 循环体执行为 1  第二部分时间复杂度为 O(n)    

此时时间复杂度为 max(O(n^2), O(n)),即 O(n^2)。

  1. 对于条件判断语句,总的时间复杂度等于其中 时间复杂度最大的路径 的时间复杂度。
def func6(num):
    if (num >= 0):
        for i in range(num):                # 循环次数为 num
            for j in range(num):            # 循环次数为 num
                print("Hello, World!\n")    # 循环体执行为 1  第一部分时间复杂度为 O(n^2)
    else:
        for k in range(num):                # 循环次数为 num
            print("Hello, World!\n")        # 循环体执行为 1  第二部分时间复杂度为 O(n)    

此时时间复杂度为 max(O(n^2), O(n)),即 O(n^2)。

时间复杂度分析的基本策略是:从内向外分析,从最深层开始分析。如果遇到函数调用,要深入函数进行分析。

最后,我们来练习一下

  • 基础题
    求该方法的时间复杂度
def  exe1(n):
    for i in range(n):                  # 循环次数为 n
        for j in range(i, n):           # 循环次数为 n - i
            print("Hello, World!\n")    # 循环体时间复杂度为 O(1)                     

参考答案:
当 i = 0 时,内循环执行 n 次运算,当 i = 1 时,内循环执行 n - 1 次运算……
当 i = n - 1 时,内循环执行 1 次运算。
所以,执行次数 f(n) = n + (n - 1) + (n - 2)……+ 1 = n(n + 1) / 2 = n^2 / 2 + n / 2
f(n)的最高次是n^2

所以 T(n) = O(f(n)) = O(n^2 / 2 + n / 2) = O(n^2) 该算法的时间复杂度为 O(n^2)

  • 进阶题
    求该方法的时间复杂度
def  exe2(n):
    for i in range(2, n):             
        i *= 2                          # 循环体时间复杂度为 O(1)
        print("Hello, World!\n")        # 循环体时间复杂度为 O(1)                     

参考答案:
假设循环次数为 t,则循环条件满足 2^t < n。
可以得出,执行次数t = log(2)(n),即 f(n) = log(2)(n),
可见时间复杂度为 T(n) = O(log(2)(n)),即 O(log n)。

  • 再次进阶
    求该方法的时间复杂度
def exe3(n):
    if (n <= 1):
        return 1
    else:
        return exe3(n - 1) + exe3(n - 2);    

参考答案:
显然运行次数,f(0) = f(1) = 1,同时 f(n) = f(n - 1) + f(n - 2) + 1,这里的 1 是其中的加法算一次执行。
显然 f(n) = f(n - 1) + f(n - 2) 是一个斐波那契数列,通过归纳证明法可以证明,
当 n >= 1 时 f(n) < (5/3)^n,同时当 n > 4 时 f(n) >= (3/2)^n。

所以该算法的时间复杂度可以表示为 O((5/3)^n),简化后为 O(2^n)。

参考

  1. https://www.jianshu.com/p/f4cca5ce055a
  2. https://www.zhihu.com/question/20196775
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值