复杂度分析
文章目录
- 如何分析、统计算法的执行效率和资源消耗?
- 为什么需要复杂度分析?
- 测试结果非常依赖测试环境
- 测试结果受数据规模的影响很大
- 大O复杂度表示法
- 时间复杂度分析
- 只关注循环次数最多的一段代码
- 加法法则:总复杂度等于量级最大的那段代码的复杂度
- 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
- 几种常见的复杂度分析实例
- 1.O(1)
- 2.O(logn) O(nlogn)
- 3.O(m+n)、O(m*n)
- 空间复杂度分析
- 常见复杂度
- 思考
- 最好最坏时间复杂度
- 平均情况时间复杂度
- 均摊时间复杂度
- 思考
如何分析、统计算法的执行效率和资源消耗?
为什么需要复杂度分析?
测试结果非常依赖测试环境
- 同样一段代码,用不同的处理器,执行时间不同
- 不同的代码在不同的机器上运行,执行时间不同
测试结果受数据规模的影响很大
- 测试数据规模太小,测试结果无法真实的反应算法的性能。
- 需要一个不用具体的测试数据来测试,就可以粗略地估计算法的执行效率,这就需要复杂度分析。
大O复杂度表示法
int cal(int n) {int sum = 0;int i = 1;for (; i <= n; ++i) {sum = sum + i;}return sum;}
- 如下代码表示1到n的累加和,估算这段代码的执行时间。
- CPU的角度看,每行代码都执行类似的操作:读数据-运算-写数据。
- 假设每行代码执行的时间相同,都是unit_time,所以这段代码的执行时间就是(2n+2)*unit_time。
- 所有代码的执行时间T(n)与每行代码的执行次数成正比。
所有代码的执行时间T(n)与每行代码的执行次数n成正比

- T(n)代表代码执行的时间
- n表示数据规模的大小
- f(n)代表每行代码执行的次数总和
- 大O时间复杂度实际上并不具体表示代码真正执行的时间
- 表示代码执行时间随数据规模增长的变化趋势,也称渐进时间复杂度,简称时间复杂度
- 当n很大时,公式中的低阶、常量、系数三部分并不左右增长趋势,都可以忽略
时间复杂度分析
只关注循环次数最多的一段代码
大O这种复杂度只是表示一种变化趋势。所以,我们在分析一个算法,一段代码的时间复杂度的时候,也只关注循环执行次数最多的那一段代码就可以了。这段核心代码执行次数的n的量级,就是整段要分析代码的时间复杂度。
加法法则:总复杂度等于量级最大的那段代码的复杂度
乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
int cal(int n){int ret = 0;int i = 1;for(;i<=n;++i){ret = ret+f(i);}
}
inf f(int n){int sum = 0;int i=1;for(;i<=n;++i){sum = sum+1;}return sum;
}
我们可以把乘法法则看成是嵌套循环。
几种常见的复杂度分析实例
- 多项式量级
- 常量阶O(1)
- 对数阶O(logn)
- 线性阶O(n)
- 线性对数阶O(nlogn)
- 平方阶O(n^2) 立方阶O(n^3) k次方阶O(n^k)
- 非多项式量级
- 指数阶O(2^n)
- 阶乘阶O(n!)
1.O(1)
O(1)只是常量级时间复杂度的一种表示方法,并不是指只执行了一行代码。如下代码,即便有3行,时间复杂度也是O(1),而不是O(3)
int i = 8;
int j = 10;
int sum - i+j;
一般情况下,只要算法中不存在循环语句、递归语句,即便有成千上万行的代码,其时间复杂度也为O(1)
2.O(logn) O(nlogn)
对数时间复杂度非常常见,同时也是最难分析的一种时间复杂度。举例如下:
int i = 1
while(i <= n){i=i*2;
}
从代码中可以看出,变量 i 的值从 1 开始取,每循环一次就乘以 2。当大于n时,循环结束。实际上变量i的取值就是一个等比数列

所以这段代码的时间复杂度就是O(log2n),通过换底公式,我们忽略对数的“底”,统一表示为O(logn),根据乘法法则,可以很容易理解O(nlogn),比如归并排序、快速排序的时间复杂度都是O(nlogn)
3.O(m+n)、O(m*n)
代码的复杂度由两个数据的规模来决定,代码如下:
int cal(int m,int n){int sum_1 = 0;int i = 1;for(;i<m;++i){sum_1 = sum_1+i;}int sum_2 = 0;int j = 1;for(;j<n;++j){sum_2 = sum_2+j;}return sum_1+sum_2;
}
m和n表示两个数据规模,我们事先无法评论m和n谁的量级大,所以我们在表示复杂度的时候,就不能简单的用加法法则,省略掉其中一个。所以上面代码的时间复杂度就是O(m+n)。但是乘法法则继续有效。
空间复杂度分析
时间复杂度的全称是渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系。 类比一下,空间复杂度就是渐进空间复杂度,表示算法的存储空间与数据规模之间的增长关系。
void pring(){int i = 0;int[] a = new int[n];for(;i<n;++i){a[i] = i*i;}for(i=n-1;i>=0;--i){print out a[j];}
}
第二行代码中,我们申请了一个空间存储变量i,但是它是常量级别的,跟数据规模n没有关系,所以可以忽略。第三行申请了一个大小为n的int类型数组,除此之外,剩下的代码都没有占用更多的空间,所以整段代码的空间复杂度就是O(n)
常见复杂度

思考
有人说,我们项目之前都会进行性能测试,再做代码的时间、空间复杂度分析,是不是多此一举呢?每段代码都分析一下时间复杂度、空间复杂度,是不是很浪费时间呢?你怎么看待这个问题?
- 渐进时间、空间复杂度分析为我们提供了一个很好的理论分析方向,并且它是宿主平台无关的,能够让我们对我们程序或算法有一个大致的认识,让我们知道,比如在最坏下程序的执行效率如何,同时也为我们提供了一个不错的桥梁,我们可以说,算法1的时间复杂度是O(n),算法2的时间复杂度为O(logN),这样我们立刻就对不同的算法有了一个“效率”上的感性认识。
- 渐进时间、空间复杂度分析只是一个理论模型,只能提供给粗略的估计分析,我们不能直接断定就觉得O(logN)的算法一定优于O(n),针对不同的宿主环境,不同的数据集,不同的数据量的大小,在实际应用上面可能真正的性能会不同,个人觉得,针对不同的实际情况,进而进行一定的性能基准测试是很有必要的,比如在统一一批手机上(同样的硬件,系统等等)进行横向基准测试,进而选择适合特定应用场景下的最有算法。
- 渐进式时间,空间复杂度分析与性能基准测试并不冲突,而是相辅相成的,但是一个低阶的时间复杂度程序有极大的可能性会优于一个高阶的时间复杂度程序,所以在实际编程中,时刻关心理论时间,空间度模型是有助于产出效率高的程序的,同时,因为渐进式时间,空间复杂度分析只是提供一个粗略的分析模型,因此也不会浪费太多时间,重点在于在编程时,要具有这种复杂度分析的思维。
最好最坏时间复杂度
//n表示array数组的大小
int find(int[] array,int n,int x){int i = 0;int pos = -1;for(int i=0;i<n;++i){if(array[i] == x){pos = i;}}return pos;
}
你应该可以看出来,这段代码要实现的功能是,在一个无序的数组(array)中,查找变量x出现的位置。如果没有找到,就返回-1。这段代码的时间复杂度为O(n),我们在寻找的过程中如果提前找到了,可以提前结束,所以可以优化
int find(int[] array,int n;int x){int i = 0;int pos = -1;for(;i<n;++i){if(array[i] == x){pos = x;break;}}return pos;
}
那么问题就来了,优化后的代码时间复杂度还是O(n)吗?
- 如果数组中的第一个元素刚好是要查找的变量x,那就不需要继续遍历剩下的n-1个数据了,时间复杂度就是O(1)
- 如果数组中不存在变量x,那我们就需要把整个数组都遍历一遍,时间复杂度就变成O(n)
不同情况下,这段代码的时间复杂度是不一样的。
所以需要引入三个概念:最好情况时间复杂度、最坏情况时间复杂度、平均情况时间复杂度。
最好:最理想情况下,执行这段代码的时间复杂度。
最坏:最糟糕的情况下,执行这段代码的时间复杂度。
平均情况时间复杂度
- 最好情况时间复杂度和最坏情况时间复杂度对应的都是极端情况下的的代码复杂度,发生的概率其实并不大。为了更好地表示平均情况下的复杂度,我们需要引入另一个概念:平均情况时间复杂度,后面我简称为平均时间复杂度。
- 要查找的变量x在数组中的位置,有n+1种情况:在数组的0~n-1个位置和不在数组中。每种情况下,查找需要遍历的元素个数累加起来,然后再除以n+1,就可以得到需要遍历的元素个数的平均值。

平均时间复杂度为O(n)
这个结论是正确的,但是计算过程稍微有点问题,因为这n+1中情况,出现的概率并不是一样的。
假设在数组中与不在数组中的概率都为1/2,另外0n-1之间的概率一样,为1/n,所以根据概率乘法法则,要查找的数据出现在0n-1中任意位置的概率就是1/(2n)。
所以将每种情况发生的概率也考虑进去,那平均时间复杂度的计算过程就变成了这样:

这个值就是概率论中的加权平均,也叫作期望值,所以平均时间复杂度的全称应该叫加权平均时间复杂度和期望时间复杂度。
均摊时间复杂度
均摊时间复杂度对应的分析方法,摊还分析(或者叫平摊分析)。
均摊时间复杂度应用的场景更加特殊,更加有限。
int[] array = new int[n];
int count = 0;void insert(int val){if(count == array.length){int sum = 0;for(int i=0;i<array.length;++1){sum = sum+1;}array[0] = sum;count = 1;}array[count] = val;++count;
}
- 最好情况时间复杂度O(1)
- 最坏情况下,数组中没有空闲空间,需要先做一遍数组的遍历求和,然后将数据插入,所以最坏情况时间复杂度为O(n)
- 平均时间复杂度为O(1)
概率论方法分析:
假设数组长度为n,可以分为n种情况,每种情况的时间复杂度为O(1),除此之外还有一种额外的情况,就是在数组没有空闲空间时插入一个数据,这个时候的时间复杂度为O(n)。而且这n中情况发生的概率是一样的,都是1/(n+1),所以根据加权平均的计算方法,所以平均时间复杂度为:

如何使用摊还分析来分析均摊时间复杂度呢?
每一次O(n)的插入操作,都会跟着n-1次O(1)的插入操作,所以把耗时多的那次操作均摊到接下来的n-1次耗时少的操作上,均摊下来,这一组连续的操作的均摊时间复杂度就是O(1)。这就是均摊分析的大致思路。
均摊时间复杂度就是一种特殊的平均时间复杂度。
思考
int[] array = new int[10];
int len = 10;
int i = 0;void add(int element){if(i>= len){int[] new_array = new int[len*2];for(int j=0;j<len;++j){new_array[j] = array[j]; }array = new_array;len = len*2;}array[i] = element;++i;
}
- 最好:O(1)
- 最坏:O(n)
- 平均:O(1)
- 均摊:O(1)
相关文章:
复杂度分析
文章目录 如何分析、统计算法的执行效率和资源消耗?为什么需要复杂度分析?测试结果非常依赖测试环境测试结果受数据规模的影响很大 大O复杂度表示法时间复杂度分析只关注循环次数最多的一段代码加法法则:总复杂度等于量级最大的那段代码的复杂…...
Linux安装jrockit-jdk1.6.0_29-R28.2.0-4.1.0-linux-x64
下载软件:jrockit-jdk1.6.0_29-R28.2.0-4.1.0-linux-x64.bin 执行安装 ./jrockit-jdk1.6.0_29-R28.2.0-4.1.0-linux-x64.bin 安装提示,一路next,注意第二步修改安装的路径,请修改成: <------------------------ O…...
7.2 怎样定义函数
7.2.1 为什么要定义函数 主要内容: 为什么要定义函数 C语言要求所有在程序中用到的函数必须“先定义,后使用”。这是因为在调用一个函数之前,编译系统需要知道这个函数的名字、返回值类型、功能以及参数的个数与类型。如果没有事先定义&…...
Chrome扩展V2到V3的变化
Chrome扩展manifest V3变化、升级迁移指南_chrome_ZK645945-华为云开发者联盟 (csdn.net) 1.background //V2 "background": "background.js"//V3 "background": {"service_worker": "background.js"} 2.executeScript …...
lock、tryLock、lockInterruptibly有什么区别?
lock、tryLock 和 lockInterruptibly 都是用于线程同步的方法,但它们有不同的行为和用途: lock() 方法:lock() 方法是 Java 中 Lock 接口定义的一部分,它用于获取锁并阻塞当前线程,直到锁可用为止。如果锁当前被其他线程占用,lock() 方法会导致当前线程阻塞,直到锁被释放…...
mysql面试题5:索引、主键、唯一索引、联合索引的区别?什么情况下设置了索引但无法使用?并且举例说明
该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:说一说索引、主键、唯一索引、联合索引的区别? 索引、主键、唯一索引和联合索引是数据库中常用的索引类型,它们有以下区别: 索引:索引是一种数…...
数据集笔记:纽约花旗共享单车od数据
花旗共享单车公布的其共享单车轨迹数据,包括2013年-2021年曼哈顿、布鲁克林、皇后区和泽西城大约14500辆自行车和950个站点的共享单车轨迹数据 数据地址:Citi Bike System Data | Citi Bike NYC | Citi Bike NYC 性别(0未知;1男&…...
为什么 0.1+0.2 不等于 0.3
为什么 0.10.2 不等于 0.3 在 JavaScript 中,0.1 0.2 的结果不等于 0.3,这是因为在 JavaScript 中采用的是双精度浮点数格式(64 位),而在这种格式下无法精确表示某些小数,因此在进行计算时会出现精度误差。…...
huggingface_hub v0.17 现已发布
InferenceClient 现在支持所有任务!💥,感谢社区的巨大努力,新添加的任务包括: 对象检测文本分类Token 分类翻译问题回答表格问题回答填充掩码表格分类表格回归文档问题回答视觉问题回答零样本分类 这些方法还支持使用 …...
机器学习——一元线性回归构造直线,并给出损失函数
目 录 Question 问题分析 1.概念补充 2.流程分析 3.注意 具体实现 最终成果 代码 思考: Question 在二维平面有n个点,如何画一条直线,使得所有点到该直线距离之和最短 如果能找到,请给出其损失函数 问题分析 1.概念…...
OpenHarmony自定义组件介绍
一、创建自定义组件 在ArkUI中,UI显示的内容均为组件,由框架直接提供的称为系统组件,由开发者定义的称为自定义组件。在进行 UI 界面开发时,通常不是简单的将系统组件进行组合使用,而是需要考虑代码可复用性、业务逻辑…...
云原生之使用Docker部署PDF多功能工具Stirling-PDF
云原生之使用Docker部署PDF多功能工具Stirling-PDF 一、Stirling-PDF介绍1.1 Stirling-PDF简介1.2 Stirling-PDF功能 二、本次实践规划2.1 本地环境规划2.2 本次实践介绍 三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本 四、下载Stirli…...
B树和B+树的介绍和对比,以及MySQL为何选择B+树
在计算机科学中,B树和B树是常用的数据结构,用于在大规模数据集上进行高效的插入、删除和查找操作。它们在数据库管理系统、文件系统等许多实际应用中发挥着重要作用。本文将深入介绍B树和B树的结构特点、实际应用方面以及它们的优缺点,并最后…...
MD5 绕过第一式:弱比较绕过
文章目录 参考环境MD5韧性脆弱性md5() 隐式类型转换字符串连接数学运算布尔判断相等运算符 科学计数法科学计数法前缀 0E 与 0e PHP8 与 PHP 其他版本下字符串转化为数值的具体规则PHP8数值字符串优化 其他版本更为详细的讲解 字符串与字符串的弱比较字符串与数值的弱比较0e215…...
红黑树是如何实现的?
文章目录 一、红黑树的概念二、红黑树的性质三、红黑树和AVL树对比四、红黑树的插入1. 红黑树的结点定义2. 父亲的颜色3. 叔叔的颜色为红色4. 叔叔不存在5. 叔叔存在且为黑6. 插入的抽象图 五、红黑树的验证1. 检查平衡2. 计算高度与旋转次数3. 验证 六、 红黑树与AVL树的比较 …...
实验室没人导该怎么办
实验室没人教该怎么办 Q: 国内top5高校研一,课题开始老板就给了一个大方向,之后怎么做实验都是自己看文献研究的,终于开始动手做实验,才发现别人根本不想管你,宁愿抱着电脑看剧也不想教你,十分焦虑,该怎么办? A: 按照大多数实验室的惯例,老板一定会指派一个小老板…...
pytest简明教程
1. 简介 pytest是一款基于Python的测试框架。与Python自带的unittest相比,pytes语法更加简洁,断言更加强大,并且在自动测试以及插件生态上比unittest都要更加强大。 1.1. 安装pytest pip install pytest1.2. pytest命名规则 pytest默认会…...
解决方案:解决https页面加载http资源报错
HTTPS页面加载HTTP资源会报错的原因是出于安全性考虑。 HTTPS(HyperText Transfer Protocol Secure)是一种通过使用SSL/TLS加密通信来保护数据传输的协议,它确保了客户端和服务器之间的安全连接。 当HTTPS页面尝试加载非加密的HTTP资源时&a…...
嵌入式开源库之libmodbus学习笔记
socat 安装sudo apt-get install socat创建终端 socat -d -d pty,b115200 pty,b115200查看终端 ls /dev/pts/ minicom 安装 sudo apt-get install minicom链接虚拟终端 sudo minicom -D /dev/pts/3以十六进制显示 minicom -D /dev/pts/1 -H设置波特率 minicom -D /dev/pts/1…...
Spring MVC 中的数据验证技术
一、前言 在Web开发中,数据验证是不可或缺的一部分。Spring MVC 框架提供了强大的数据验证支持,可以帮助我们轻松地对用户提交的数据进行验证。 二、实现 Spring MVC 使用 JSR-303 验证规范(Hibernate Validator 是其参考实现)…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
LLMs 系列实操科普(1)
写在前面: 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容,原视频时长 ~130 分钟,以实操演示主流的一些 LLMs 的使用,由于涉及到实操,实际上并不适合以文字整理,但还是决定尽量整理一份笔…...
