【算法】算法效率分析 -- 时间空间复杂度

news2024/12/23 1:56:38

概述

算法的效率分析,主要从「时间」和「空间」两个维度进行分析。

  • 时间维度顾名思义就是算法需要消耗的时间,用术语 - “时间复杂度” 表示。
  • 空间维度代表算法需要占用的内存空间,用术语 - “空间复杂度” 表示。
    在计算机发展的早期,计算机的存储容量很小,所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度

算法本质上是一连串的计算步骤。对于同一个问题,我们可以使用不同的算法来获得相同的结果,可是在计算过程中电脑消耗的时间和资源可能有很大的区别;使用相同的算法,在不同性能的电脑中运行消耗的时间和资源也有差别。那我们如何来比较不同算法之间的优劣性呢?
答案是:大O表示法。

大O表示法 (Big O notaion),也叫大O渐进表示法。是一种数学表示法,描述在参数趋向于特定值或无穷大时函数的极限行为O代表Ordnung,意思是近似阶数。

在计算机科学中,大O表示法被用来表示算法的时间复杂度或空间复杂度。

  • 时间复杂度,全称叫"渐进时间复杂度":表达式为 T ( n ) = O ( f ( n ) ) T(n) = O(f(n)) T(n)=O(f(n))
  • 空间复杂度,全称叫"渐进空间复杂度":表达式为 S ( n ) = O ( f ( n ) ) S(n) = O(f(n)) S(n)=O(f(n))

大O表示法表示在参数N趋近于无穷大时的算法的极限行为,即时间的上限,通常这意味着最差(最费时间、最耗内存)的情况,这也是我们最关心的一点。(大O不表示具体的时间或空间,我们也不需要考虑具体的时间或空间,因为这在不同性能的电脑上是不一样的)
因此,如果f(n)是由几个项组成的,则应该按照N趋近于无穷大的原则进行简化,只保留一项:

  • 如果f(n)是几个项的总和,如1+n+n²,则只需要取最大阶,省略其它所有的项。
  • 如果f(n)是多个因子的乘积,如3*n,则可以省略任何常数,保留n
  • 如果f(n)是几个常数的总和,如1+3+2,则用1表示任何常数,BigO表达式为 O ( 1 ) O(1) O(1)

复杂度指标”:大O表示法是一种复杂度指标,它表示最差情况;除此之外还有 Ω(Big Omega)-最好情况、Θ(Big theta)-情况区间。通常来说,考虑最差的情况更有意义。

利用大O表示法,我们可以在设计程序之前就能知道某种算法的运行效果,如果这种算法的效果不适用,就没有必要浪费时间去写代码了。

时间复杂度

时间复杂度 T ( n ) = O ( f ( n ) ) T(n) = O(f(n)) T(n)=O(f(n)),通常通过计算算法中基本操作的执行次数来估计,假设每个基本操作需要固定的时间来执行。
基本操作:指算法中的每条语句,语句的执行次数又被称为语句的频度。

常见的时间复杂度量级如下,按照复杂度从低到高排序。(参考Order of growth)

名称BigO表达式直观现象效率评价
常数阶 O ( 1 ) O(1) O(1)数据独立 (独立于n,与n无关,恒定时间)很棒
对数阶 O ( l o g n ) O(logn) O(logn)迭代减半 (如二分查找)
线性阶 O ( n ) O(n) O(n)每个项迭代一次可以
线性对数阶 O ( n ∗ l o g n ) O(n*logn) O(nlogn)嵌套迭代:一次线性,一次对数
平方阶 O ( n 2 ) O(n^2) O(n2)嵌套迭代:两次线性糟糕
立方阶 O ( n 3 ) O(n^3) O(n3)嵌套迭代:三次线性糟糕
k次方阶 O ( n k ) O(n^k) O(nk)嵌套迭代:k次线性糟糕
指数阶 O ( 2 n ) O(2^n) O(2n)组合糟糕
阶乘 O ( n ! ) O(n!) O(n!)全排列糟糕

函数曲线 (渐进趋势)
在这里插入图片描述

常数阶 O ( 1 ) O(1) O(1)

int x = 0;
int y = 1;
int temp = x;
x = y;
y = temp;

没有循环或递归等复杂逻辑,无论代码执行多少行,代码复杂度都为 O ( 1 ) O(1) O(1)

1表示常数,这个代码有5行,不正确的表示是 O ( 5 ) O(5) O(5),但用1表示常数,因此简化为 O ( 1 ) O(1) O(1)

O ( 1 ) O(1) O(1)例子有:查找数组中指定下标的元素、从哈希表取出元素;+ - * / 运算、位运算;等等。

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

for (int i = 1; i <= n; i++) {
    x++;
}

在这段代码中,for循环会执行n遍,因此计算消耗的时间(基本操作的执行次数)是随着n的变化而变化的,此类代码都可以用 O ( n ) O(n) O(n)来表示其时间复杂度。

表达式的简化过程为: O ( 1 + 3 n ) O(1+3n) O(1+3n) -> O ( n ) O(n) O(n)

  • 1表示int i = 1语句只执行一次。
  • 3n表示随着n的每次迭代都分别执行一次i<=ni++x++
  • 当n趋向于无穷大时,1和3的意义都不大,所以简化为 O ( n ) O(n) O(n)

O ( n ) O(n) O(n)例子有:遍历数组、桶排序(桶排序时间复杂度为什么是O(n)) 等等。

对数阶 O ( l o g n ) O(logn) O(logn)

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

在这段代码中,每次i都会被乘以2,意味着每次i都离n更进一步。那需要多少次循环 i 才能等于或大于 n 呢,也就是求解2的x次方等于n,答案是 x=log₂n 。也就是说循环 log₂n 次之后,出现i >= n,这段代码就结束了。所以此段代码的复杂度为: O ( l o g n ) O(logn) O(logn)

log₂n 的核心是"迭代减半、对半分"。

log₂2=1  log₂8=3  log₂32=5  log₂1024=10
# 可以看出来,越到后面(n越大),消耗的时间相对就越少(增速缓慢)。

log₂n 复杂度的BigO表示法为 O ( l o g n ) O(logn) O(logn),省略了底数。那么有没有 O ( l o g 3 n ) O(log_3n) O(log3n) O ( l o g 4 n ) O(log_4n) O(log4n)呢 – 没有!所有的底数都被省略,都表示为 O ( l o g n ) O(logn) O(logn)。底数对时间当然是有影响的,但无论底数是什么,对数阶的渐进意义(函数曲线)是一样的。
在这里插入图片描述

O ( l o g n ) O(logn) O(logn)例子有:二分查找、二叉查找树、gcd欧几里得的辗转相除 等等。

线性对数阶 O ( n ∗ l o g n ) O(n*logn) O(nlogn)

线性对数阶 O ( n ∗ l o g n ) O(n*logn) O(nlogn)很好理解,就是将复杂度为 O ( l o g n ) O(logn) O(logn)的代码循环n遍:

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

因为每次循环的复杂度为 O ( l o g n ) O(logn) O(logn),所以n * logn = O ( n ∗ l o g N ) O(n*logN) O(nlogN)

O ( n ∗ l o g n ) O(n*logn) O(nlogn)例子有:

  • 对长度为n的数组进行高效的排序:经典三大 O ( n ∗ l o g n ) O(n*logn) O(nlogn)排序算法:快排、堆排、归并。
  • 贪心题目的时间复杂度多为此。因为通常需要排序。
  • 克鲁斯卡尔根据图的边生成最小生成树,边的数量n,时间复杂度 O ( n ∗ l o g n ) O(n*logn) O(nlogn)

次方阶 O ( n 2 ) O(n^2) O(n2) O ( n 3 ) O(n^3) O(n3) O ( n k ) O(n^k) O(nk)

O ( n 2 ) O(n^2) O(n2) 就是将循环次数为n的代码再循环n遍:

for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= n; j++) {
        x++;
    }
}

再看一个例子:

for (int i = 1; i <= n; i++) {
    x++;
}
for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= n; j++) {
        x++;
    }
}

这个例子的时间复杂度是 O ( n + n 2 ) O(n + n^2) O(n+n2),在n趋向于无穷大的情况下,第一个n的意义就不大了,因此BigO表达式应该简化为 O ( n 2 ) O(n^2) O(n2)

关于更高阶的次方阶,都是类似的意思, O ( n 3 ) O(n^3) O(n3)相当于三层循环, O ( n k ) O(n^k) O(nk)相当于k层循环。

O ( n 2 ) O(n^2) O(n2)例子有:冒泡排序、选择排序、普里姆的最小生成树算法(节点为n) 等等。

O ( n 3 ) O(n^3) O(n3)例子有:遍历3D世界(三维地图)的所有方块(即三维数组) 等等。

O ( n m ) O(nm) O(nm)

O ( n 2 ) O(n^2) O(n2)的本质就是 n * n,如果我们将内层的循环次数改为m:

for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
        x++;
    }
}

那么复杂度就变为 n * m = O ( m n ) O(mn) O(mn),因为BigO表达的是上限,所以这种表达式属于 O ( n 2 ) O(n^2) O(n2),它并没有脱离这种渐进意义。

但是, O ( n 2 ) O(n^2) O(n2)可能并不是表达 O ( m n ) O(mn) O(mn)的最佳方式,如果m是一个范围从0到n的可调参数,那么 O ( m n ) O(mn) O(mn)更能表达此算法的复杂度。

stack overflow - Is time Complexity O(nm) equal to O(n^2) if m <= n

指数阶 O ( 2 n ) O(2^n) O(2n)

func fib(n int) int {
        if n == 0 {
                return 0
        } else if n == 1 {
                return 1
        } else {
                return fib(n-1) + fib(n-2)
        }
}

这是一个"斐波那契函数",用来计算第n个斐波那契数。(此函数对应的"斐波那契数列"从0开始,有的是从1开始,写法上略有区别,但这并不重要)

如下图,要想得到 fib(6) 需要先计算 fib(5)fib(4) ; 想要得到 fib(5)需要先计算 fib(4)fib(3) ;以此类推。将红色框内的 fib(2)fib(1) 移动到上一层的最右侧,就能够明显看出来总的执行的次数是一个等比数列求和。根据求和公式 S n = a n q − a 1 q − 1 , ( q ≠ 1 ) S_n=\frac{a_nq-a_1}{q-1},(q≠1) Sn=q1anqa1,(q=1),得总执行次数为 2 n − 1 2^n-1 2n1,省略常数1,因此时间复杂度为 O ( 2 n ) O(2^n) O(2n)
在这里插入图片描述

O ( 2 n ) O(2^n) O(2n)例子是:斐波那契函数、汉诺塔。这都是很糟糕的递归。

有一个比较有意思的使用场景是,在进行网络开发时,将斐波那契或汉诺塔函数作为一个耗时的响应任务,使其能够根据请求参数n的大小改变响应时长。在RabbitMQ官方教程中的RPC工作模式的示例中,就使用了这种方法。

阶乘 O ( n ! ) O(n!) O(n!)

阶乘就是全排列的所有可能数量。就不再例举代码了。

O ( n ! ) O(n!) O(n!)例子是:获得n个数字的所有排列方式、猴子排序🐒(最奇葩的排序方式之一)。


空间复杂度

空间复杂度 S ( n ) = O ( f ( n ) ) S(n) = O(f(n)) S(n)=O(f(n)),是对算法 输入使用的内存空间 和 执行期间临时占用的任何内存空间(称为辅助空间) 的度量,这些存储空间是通过参数来表现的。因此,一个算法的空间复杂度是通过它的形参和局部变量所使用的存储空间数估计的。

空间复杂度一般只有三种情况

名称BigO表达式直观现象效率评价
常数阶 O ( 1 ) O(1) O(1)创建了常数个变量很棒
线性阶 O ( n ) O(n) O(n)创建了n个变量可以
平方阶 O ( n 2 ) O(n^2) O(n2)嵌套迭代:两次线性糟糕

常数阶 O ( 1 ) O(1) O(1)

int x = 0;
int y = 0;
x++;
y++;

这段代码执行所需要的空间不会随着某个变量的大小而发生变化,x, y所分配的空间是确定的,不随着处理数据量变化,因此空间复杂度为 O ( 1 ) O(1) O(1)

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

int[] array = new int[n];

在这段代码中,创建了一个长度为n的数组,这个数组占用的大小为n。这段代码的空间复杂度取决于数组的长度,也就是n,所以空间复杂度为 O ( n ) O(n) O(n)

平方阶 O ( n 2 ) O(n^2) O(n2)

int[][] array = new int[n][n];

在这段代码中,创建了一个二维数组(矩阵),这个数组占用的大小为n*n。所以空间复杂度为 O ( n 2 ) O(n^2) O(n2)

不断增加数组的维度,则还有 O ( n 3 ) O(n^3) O(n3)、…、 O ( n k ) O(n^k) O(nk)


总结

对于一个算法,其时间复杂度和空间复杂度往往是相互影响的。

  • 当追求一个较好的时间复杂度时,可能会使空间复杂度的性能变差,即可能导致占用较多的存储空间;
  • 反之,当追求一个较好的空间复杂度时,可能会使时间复杂度的性能变差,即可能导致占用较长的运行时间。

因此,当设计一个算法(特别是大型算法)时,要综合考虑算法的各项性能,算法的使用频率,算法处理的数据量的大小,算法描述语言的特性,算法运行的机器系统环境等各方面因素,才能够设计出比较好的算法 – 考虑并采取的方法有"时间换空间"、“空间换时间”。

算法的时间复杂度和空间复杂度合称为算法的复杂度

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/699026.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

保持无损连接的BCNF分解算法

建议在看之前熟悉候选键的求法&#xff0c;不清楚的可以转到这里来&#xff1a; http://t.csdn.cn/fW30Q 步骤&#xff1a; INPUT:关系模式R以及在R上成立的函数依赖集F 1.初始化P{R} 2.若P中的所有关系模式S都是BCNF&#xff0c;则转步骤(4) 3.若P中有一个模式S不是BCNF&am…

Linux——IP协议2

目录 公网IP ​编辑 特殊的IP地址 IP地址的数量限制 私有IP地址和公网IP地址 路由 数据链路层 认识以太网 以太网帧格式 如何解包和封装&#xff0c;交付及分用 重谈局域网通信原理 认识MTU MTU对于TCP协议的影响 查看硬件地址和MTU ARP协议 模拟ARP请求 …

ros2内结合gazebo和rviz进行yolov8检测记录

前提&#xff1a;第一次接触ros2, 遇到的问题解决方式不一定准确&#xff0c;只是这次我尝试成功了&#xff0c;想和大家分享一下。 ubuntu20.04系统 目录 1. ros2 1.1 ros2是啥&#xff1f; 1.2 ros2的版本和ubuntu版本的对应关系&#xff0c;当下入门尤其是ubuntu20.04系…

【物理摩擦力图像】对摩擦力大小的因素研究图像

摩擦力与编程 两个相互接触并挤压的物体&#xff0c;当它们发生相对运动或具有相对运动趋势时&#xff0c;就会在接触面上产生阻碍相对运动或相对运动趋势的力&#xff0c;这种力叫做摩擦力&#xff08;Ff或f&#xff09;。 摩擦力与正压力&#xff08;B物体上的A物体产生的压…

数据库实验—复杂查询

查询20161151班的学生在大学一年级选修的课程情况&#xff0c;查询结果要显示学号(Sno)、姓名(Sname)、专业名(Mname)、选课的课程号(Cno)、选课的课程名称(Cname)及成绩&#xff08;Grade)&#xff0c;并按照学号、课程号升序排序 select Sno, Sname,Mname, Cno,Cname,Grade f…

Windows11安装WSL2(Ubuntu20.04)

以管理员身份打开 PowerShell&#xff0c;输入以下命令安装&#xff1a; wsl --install dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /no…

【历史上的今天】6 月 29 日:SGI 和 MIPS 合并;微软收购 PowerPoint 开发商;新闻集团出售 Myspace

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 6 月 29 日&#xff0c;在 2008 年的今天&#xff0c;CNNIC 发布《第 22 次中国互联网络发展状况统计报告》&#xff0c;截至 2008 年 6 月底&#xff0c;中国…

draggable里包裹的卡片,卡片里有个input,点击input聚焦无效。

在input标签上加pointerdown.stop.native <el-input placeholder"请输入" pointerdown.stop.native v-model"dataForm.nickName" :style"{width:180px}" suffix-icon"el-icon-search" lazy />

TLD7002学习笔记(三)-使用S32K144EVB烧录TLD7002

文章目录 1. 前言2. 烧录数据准备2.1 OTP Wizard的下载与安装2.2 OTP Wizard的配置2.3 OTP寄存器烧录数据提取 3. OTP烧录和仿真的流程3.1 OTP烧录流程3.2 OTP仿真流程 4. 验证测试4.1 测试代码4.2 测试环境4.3 测试情况 5. 参考资料 1. 前言 本篇文章是TLD7002学习笔记的第三…

设计模式第18讲——中介者模式

目录 一、什么是中介者模式 二、角色组成 三、优缺点 四、应用场景 4.1 生活场景 4.2 java场景 五、代码实现 5.0 代码结构 5.1 抽象中介者&#xff08;Mediator&#xff09;——LogisticsCenter 5.2 抽象同事类&#xff08;Colleague&#xff09;——Participant 5…

Beego之Bee安装(Windows)以及创建,运行项目

一.简介 Bee是什么&#xff1f; bee工具是一个为了协助快速开发 Beego 项目而创建的项目&#xff0c;通过 bee 可以很容易的进行 Beego 项目的 创建、热编译、开发、测试和部署 Beego中文文档 Beego中文文档: Beego简介 安装前提 在安装bee之前&#xff0c;首先得提前安装好Go的…

【T3】打开财务报表提示不能登陆到服务器,请检查服务器配置。

【问题描述】 在使用畅捷通T3软件的时候&#xff0c; 打开【财务报表】提示&#xff1a;不能登陆到服务器&#xff08;GUPR7FM&#xff09;&#xff0c;请检查服务器配置。 但是打开【总账系统】&#xff0c;填制凭证、查看报表等操作都正常。 【解决方法】 由于操作【总账系…

【Java高级编程】Java反射机制

Java反射机制 1、反射的概述1.1、本章的主要内容1.2、关于反射的理解1.3、体会反射机制的“动态性”1.4、反射机制能提供的功能1.5、相关API 2、Class类的理解与获取Class的实例2.1、Class类的理解2.2、获取Class实例的几种方式2.3、总结&#xff1a;创建类的对象的方式2.4、Cl…

生产者与消费者问题

本篇文章我们使用C探讨一下生产者与消费者问题. 1. 多线程的引入 我们学习了操作系统, 知道了进程和线程的概念, 但是如果不进行代码实战的话, 会很难理解它们. 特别是编程的初学者(比如我), 在了解了进程和线程后通常会感到疑惑: 多线程怎么用? 为啥我平时写代码没有使用到…

ATM机项目实战——准备

项目介绍 大概功能&#xff1a; 实现一个模拟ATM机存款、取款、转账功能的一个系统&#xff0c;可以查看打印交易明细&#xff0c;后台用户可以管理用户账户卡信息。 适合人群&#xff1a; 在校计算机专业的大学生&#xff0c;愿意从事JAVA开发的人群&#xff0c;具体基础的…

记一次系统的jar包本地化方案

重在思路 行内容器环境,tomcat版本是8.5,导致jar包冲突,优先调用了jar包中的方法,致使同名同路径下,改写的类方法失效,报java.lang.NoSuchMethodError错误,删除对应class后需要重新更新jar包到行内maven私服仓库,流程复杂,且不清楚哪些地方依然有重写方法的地方,设置tomcat加载…

基于SSM+jsp的二手车交易网站设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

DBeaver mysql socks5 代理

DBeaver mysql socks5 代理 测试连接报错 Can not read response from server. Expected to read 4 bytes, read 0 bytes before connection was unexpectedly lost.切换代理&#xff0c;连接成功 参考 DBeaver 文档 代理配置

谈一谈面试算法的几个常见误区

下一篇我们就开始梳理面试算法的体系了&#xff0c;今天呢&#xff0c;有个问题有必要谈一下——如何正确对待算法&#xff0c;如何征服算法。 目录 1.如何平常心对待算法 2.关于准备算法面试的一些误区 1.如何平常心对待算法 在老师之前算法班的群里&#xff0c;有位同学分…

vue3创建项目(五)vite配置端口号

Vue3配置代理跨域和服务端口等配置&#xff0c;可以在 vite.config.js 进行配置。(类似用 vue-cli 的 vue.config.js&#xff09; https://cn.vitejs.dev/config/第一步&#xff1a;首先在根目录下面创建vue.config.js import { defineConfig } from vite import vue from vite…