Hello算法2:复杂度分析

Hello算法2:复杂度分析

本文是基于k神的Hello 算法的读书笔记,请支持实体书。
https://www.hello-algo.com/chapter_paperbook/

算法效率

算法效率评估

设计算法时,我们追求以下两个目标:

  • 找出解法
  • 找出最优解

最优解通常包含两个指标:

  • 时间:算法运行的时间
  • 空间:算法占用的内存

评估的方法分为两种:

实际测试和理论评估

实际测试

找一台计算机实际运行代码,比较两个算法的效率。

这样虽然很直观,但是有以下缺陷

  • 难以排除测试环境的干扰:两台计算机配置不同,在上面运行某算法可能会得到不同的结果
  • 完整的测试很耗费资源:某些算法对于少量数据和巨量数据表现差别很大,这就需要设计测试时尽量覆盖所有情况

理论估算

也就是复杂度分析,它描述随着输入数据大小的增加,算法执行的时间和空间的增长趋势。

它客服了实际测试的缺点,体现如下:

  • 无需测试环境,结果适用于各平台
  • 可以体现不同数据量下的效率,特别是针对大数据

迭代和递归

在执行算法时,重复执行某个任务很常见。

迭代

常见的迭代有for循环和while循环

for循环

比如计算1+2+n的值,for循环的时间复杂度取决于n的值,它是线性相关的

while循环

初始化条件和条件更新的步骤独立于循环过程,所以while循环更自由

循环的嵌套

循环中可以嵌套循环,此时复杂度就变成了n的平方

递归

递归是指通过调用函数自身来解决问题,主要包含两个阶段

  1. 递:不断深入的调用自身,通常是不断传入更小的参数,直到达到终止条件
  2. 归:触发终止条件后,程序开始逐层返回
def recur(n: int) -> int:
    """递归"""
    # 终止条件
    if n == 1:
        return 1
    # 递:递归调用
    res = recur(n - 1)
    # 归:返回结果
    return n + res

调用栈

每次调用自身,计算机都会开辟新的内存区域,存储局部变量、调用地址和其他变量,因此

  • 递归通常比迭代更耗费内存空间
  • 递归的时间效率通常更低

递归深度过多,可能会溢出

尾递归

在返回前的最后一步才进行递归调用中调用自身,这种方式某些语言可以被编译器或解释器优化,使其在空间效率上与迭代相当。但比如python就不支持。

def tail_recur(n, res):
    """尾递归"""
    # 终止条件
    if n == 0:
        return res
    # 尾递归调用
    return tail_recur(n - 1, res + n)

递归树

以“斐波那契数列”为例,将会产生如下的递归树

给定一个斐波那契数列0,1,1,2,3,5,8,13,…,求该数列的第n个数字

def fib(n: int) -> int:
    """斐波那契数列:递归"""
    # 终止条件 f(1) = 0, f(2) = 1
    if n == 1 or n == 2:
        return n - 1
    # 递归调用 f(n) = f(n-1) + f(n-2)
    res = fib(n - 1) + fib(n - 2)
    # 返回结果 f(n)
    return res

在这里插入图片描述

时间增长趋势

如下三个算法:

# 算法 A 的时间复杂度:常数阶
def algorithm_A(n: int):
    print(0)
# 算法 B 的时间复杂度:线性阶
def algorithm_B(n: int):
    for _ in range(n):
        print(0)
# 算法 C 的时间复杂度:常数阶
def algorithm_C(n: int):
    for _ in range(1000000):
        print(0)

时间增长趋势如下:

在这里插入图片描述

可以看出时间复杂度有如下特点:

  • 可以有效评估算法效率
  • 时间复杂度的推算更简便
  • 存在一定局限性

计算方式

第一步:统计操作数量,有以下技巧

  1. 忽略常数项
  2. 省略所有系数
  3. 循环嵌套时使用乘法
def algorithm(n: int):
    a = 1      # +0(技巧 1)
    a = a + n  # +0(技巧 1)
    # +n(技巧 2)
    for i in range(5 * n + 1):
        print(0)
    # +n*n(技巧 3)
    for i in range(2 * n):
        for j in range(n + 1):
            print(0)

应用上述方法后,可得操作数量是

在这里插入图片描述

第二步:逐渐判断上界

时间复杂度由 T(n) 中最高阶的项来决定。这是因为在 n 趋于无穷大时,最高阶的项将发挥主导作用,其他项的影响都可以忽略。

常见有如下复杂度:
在这里插入图片描述

相关推荐

  1. 算法复杂分析

    2024-03-26 18:38:01       53 阅读
  2. 算法复杂分析

    2024-03-26 18:38:01       47 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-26 18:38:01       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-26 18:38:01       106 阅读
  3. 在Django里面运行非项目文件

    2024-03-26 18:38:01       87 阅读
  4. Python语言-面向对象

    2024-03-26 18:38:01       96 阅读

热门阅读

  1. go语言数组的增删改查

    2024-03-26 18:38:01       46 阅读
  2. Tomcat 启动闪退问题解决方法

    2024-03-26 18:38:01       41 阅读
  3. Openstack(T) 部署Ceilometer服务 ---mongo命令error

    2024-03-26 18:38:01       43 阅读
  4. 来自chatgpt的一些回答

    2024-03-26 18:38:01       39 阅读
  5. kingbaseESV8逻辑备份还原

    2024-03-26 18:38:01       38 阅读
  6. C语言 序列中删除指定数字

    2024-03-26 18:38:01       38 阅读
  7. 如何快速上手vue框架

    2024-03-26 18:38:01       40 阅读
  8. 1、Spring CLI 简介

    2024-03-26 18:38:01       41 阅读
  9. 设置NPM镜像命令

    2024-03-26 18:38:01       34 阅读