【数据结构】6. 时间与空间复杂度
文章目录
- 一、算法效率
- 1、算法的复杂度
- 二、时间复杂度
- 1、时间复杂度的概念
- 2、大O的渐进表示法
- 3、常见时间复杂度计算
- 1)实例1
- 2)实例2
- 3)实例3
- 4)实例4
- 5)实例5
- 6)实例6
- 7)实例7
- 8)实例8
- 三、空间复杂度
- 1、空间复杂度的概念
- 2、常见空间复杂度计算
- 四、常见复杂度对比
- 五、时间复杂度的oj练习
- 1、消失的数字
- 2、轮转数组
一、算法效率
如何衡量一个算法的好坏呢?比如对于以下斐波那契数列:
long long Fib(int N)
{if(N < 3)return 1;return Fib(N-1) + Fib(N-2);
}
斐波那契数列的递归实现方式非常简洁,但简洁一定好吗?那该如何衡量其好与坏呢?
1、算法的复杂度
算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。
时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。
二、时间复杂度
1、时间复杂度的概念
时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的执行次数,为算法的时间复杂度。
// 请计算一下Func1中++count语句总共执行了多少次?
void Fun1(int N)
{int count = 0;for (int i = 0; i < N; ++i){for (int j = 0; j < N; ++j){++count;}}for (int k = 0; k < 2 * N; ++k){++count;}int M = 10;while (M--){++count;}printf("%d\n", count);
}
Func1 执行的次数 :F(N)=N2+2*N+10。
- N = 10、F(N) = 130
- N = 100 、F(N) = 10210
- N = 1000 、F(N) = 1002010
实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法。
2、大O的渐进表示法
大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
推导大O阶方法:
- 用常数1取代运行时间中的所有加法常数。
- 在修改后的运行次数函数中,只保留最高阶项。
- 如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。
使用大O的渐进表示法以后,Func1的时间复杂度为:O(N2)
- N = 10 、F(N) = 100
- N = 100 、 F(N) = 10000
- N = 1000 、F(N) = 1000000
通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。
另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)。
平均情况:任意输入规模的期望运行次数 。
最好情况:任意输入规模的最小运行次数(下界)。
例如: 在一个长度为N数组中搜索一个数据x。
最好情况:1次找到
最坏情况:N次找到
平均情况:N/2次找到
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)。
3、常见时间复杂度计算
1)实例1
void Func2(int N)
{int count = 0;for (int k = 0; k < 2 * N; ++k){++count;}int M = 10;while (M--){++count;}printf("%d\n", count);
}
函数执行了2N+10次,影响因素最大的是N,因此时间复杂度是:O(N)。
2)实例2
void Func3(int N, int M)
{int count = 0;for (int k = 0; k < M; ++k){++count;}for (int k = 0; k < N; ++k){++count;}printf("%d\n", count);
}
函数执行了N+M次,N和M的影响程度相同,因此时间复杂度是:O(N+M)。
3)实例3
void Func4(int N)
{int count = 0;for (int k = 0; k < 100; ++k){++count;}printf("%d\n", count);
}
函数执行的次数与N无关,因此时间复杂度为常数级别:O(1)。
4)实例4
//strchr:在字符串中查找指定字符的首次出现位置
const char* strchr(const char* str, int character)
{while (*str){if (*str == character)return str;else++str;}
}
最坏情况下函数需要遍历整个字符串,循环次数为N,因此时间复杂度为:O(N)。
5)实例5
//冒泡排序
void BubbleSort(int* a, int n)
{assert(a);for (size_t end = n; end > 0; --end) //外层循环:控制排序轮数{int exchange = 0; //标记是否发生交换for (size_t i = 1; i < end; ++i) //内层循环:比较相邻元素{if (a[i-1] > a[i]){Swap(&a[i-1], &a[i]); //交换元素exchange = 1; //标记发生交换}}if (exchange == 0) //若未发生交换,说明已有序break; //提前终止循环}
}
最坏的情况下,函数会执行N-1 + N-2 + ... + 2 + 1
次,求和后就是N*(N-1)/2
,影响因素最大的是N2,因此时间复杂度是:O(N2)。
6)实例6
//二分查找
int BinarySearch(int* a, int n, int x)
{assert(a);int begin = 0;int end = n-1; //注意:这里是闭区间 [begin, end]while (begin <= end) //闭区间条件:begin <= end{int mid = begin + ((end-begin)>>1); //安全的中间值计算,避免溢出if (a[mid] < x)begin = mid+1; //右半区间:[mid+1, end]else if (a[mid] > x)end = mid-1; //左半区间:[begin, mid-1]elsereturn mid; //找到目标值}return -1; //未找到
}
二分查找的最坏情况是查找区间只剩下最后一个数或者找不到,因此执行次数应该为N/2/2.../2=1
,因此 N= log 2 N \log_2 N log2N,时间复杂度为:O( log N \log N logN)。
7)实例7
//阶乘
long long Fac(size_t N)
{if (0 == N)return 1;return Fac(N - 1) * N;
}
递归执行了N+1次,因此时间复杂度为:O(N)。
8)实例8
//斐波那契数
long long Fib(size_t N)
{if(N < 3)return 1;return Fib(N-1) + Fib(N-2);
}
画图演示:

递归总共执行了2N-1-1 次,因此时间复杂度为:O(2N)。
由于时间复杂度太高,我们一般不使用递归的方式计算斐波拉契数列,而是使用迭代,如下:
//斐波拉契数
long long Fib(int N)
{long f1 = 1;long f2 = 1;long f3 = 0;for (int i = 3; i < N; i++){f3 = f1 + f2;f1 = f2;f2 = f3;}return f3;
}
现在的时间复杂度就变为了O(N)。
三、空间复杂度
1、空间复杂度的概念
空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。
空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,空间复杂度其实算的是变量的个数。空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法。
注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。
常见的空间复杂度有:O(1)、O(N)、O(N^2)。
2、常见空间复杂度计算
实例1:
//冒泡排序
void BubbleSort(int* a, int n)
{assert(a);for (size_t end = n; end > 0; --end) {int exchange = 0; for (size_t i = 1; i < end; ++i) {if (a[i-1] > a[i]){Swap(&a[i-1], &a[i]); exchange = 1; }}if (exchange == 0) break; }
}
函数内部创建了三个变量:end、exchange、i
,因此开辟的空间是常数级,空间复杂度为:O(1)。
实例2:
//递归求阶乘
long long Fac(size_t N)
{if (N == 0)return 1;return Fac(N - 1) * N;
}
每次递归的调用就会创建一个函数栈帧(空间),总共调用N+1次递归,就会开辟N+1个函数栈帧,因此空间复杂度为:O(N)。
实例3:
//斐波拉契数
long long Fib(size_t N)
{if (N < 3)return 1;return Fib(N - 1) + Fib(N - 2);
}
同样每次递归调用都会开辟一块函数栈帧,但是在这里这个空间可以重复使用,总共开辟N-1个函数栈帧,因此空间复杂度为:O(N)。
四、常见复杂度对比
一般算法常见的复杂度如下:
五、时间复杂度的oj练习
两道时间复杂度的oj练习题,如下:
1、消失的数字
点击链接:https://leetcode-cn.com/problems/missing-number-lcci/
这道题我们有三种思路,但是题目要求时间复杂度在O(N)内,因此我们需要分析一下。
思路一: 先将数组排序(qsort快速排序),再依次查找,如果下一个值不等于前一个+1,那么下一个值就是消失的数字。
时间复杂度分析: 快速排序的平均时间复杂度是O(N* log N \log N logN),查找的最坏情况下时间复杂度是O(N),发现快速排序的时间复杂度影响程度更大,因此整体的时间复杂度为:O(N log N \log N logN)。不满足题意舍去。
思路二: 求和0到N(未消失),再依次减去数组所有的元素值,最后剩下的就是消失的元素。这种情况下时间复杂度为O(N),满足题意。
int missingNumber(int* nums,int numsSize)
{int N=numsSize;int ret=(0+N)*(N+1)/2;for(int i=0;i<N;i++){ret-=nums[i];}return ret;
}
经测试可以成功运行。但是有个问题就是N太大的时候存在溢出风险。
思路三: 采用异或(异或特性:任何数和0异或都等于本身,相同的数异或等于0,异或满足交换结合律),先用0与数组中的元素一一异或,再将这个整体与0到N的整数一一异或,最后剩下的就是消失的数。
int missingNumber(int* nums, int numsSize)
{ int N=numsSize;int x=0;for(int i=0;i<N;i++){x^=nums[i];}for(int j=0;j<=N;j++){x^=j;} return x;
}
函数执行了2N+1次,因此时间复杂度为O(N),满足题意。
2、轮转数组
点击链接:https://leetcode-cn.com/problems/rotate-array/
思路一:每次实现一次旋转,总共旋转k%N次即可。
如图所示:
void rotate(int* nums, int numsSize, int k)
{int N=numsSize;k%=N;while(k--){//旋转一次int temp=nums[N-1];for(int i=N-2;i>=0;i--){nums[i+1]=nums[i];} nums[0]=temp;}
}
函数会执行K*N次,因此时间复杂度为:O(K * N),当输入的数据很大时,时间复杂度就很大,就会超出时间限制。
如图所示:
思路二:采取三步反转法。

void reverse(int* a,int left,int right)
{while(left<right){int temp=a[left];a[left]=a[right];a[right]=temp;left++;right--;}
}void rotate(int* nums, int numsSize, int k)
{int N=numsSize;k%=N;reverse(nums,0,N-k-1);reverse(nums,N-k,N-1);reverse(nums,0,N-1);
}
函数执行了N-k + k + N=2N次,因此时间复杂度为O(N),相比思路一时间复杂度就大大的减少了。
相关文章:

【数据结构】6. 时间与空间复杂度
文章目录 一、算法效率1、算法的复杂度 二、时间复杂度1、时间复杂度的概念2、大O的渐进表示法3、常见时间复杂度计算1)实例12)实例23)实例34)实例45)实例56)实例67)实例78)实例8 三…...
Python 函数全攻略:函数进阶(生成器、闭包、内置函数、装饰器、推导式)
一、默认参数中的易错点 问题: 当函数的默认参数是可变类型(如 list, dict)时,存在“坑”。 现象: def func(a2=[]): # a2 默认是一个空列表a2.append(2)print(a2)func() # 第一次调用,a2 默认为 [],输出 [2] func([]) # 传入新列表,输出 [2] func([1]) # 传入带元素的…...

基于springboot的藏文古籍系统
博主介绍:高级开发,从事互联网行业六年,熟悉各种主流语言,精通java、python、php、爬虫、web开发,已经做了多年的设计程序开发,开发过上千套设计程序,没有什么华丽的语言,只有实实在…...

重构城市应急指挥布控策略 ——无人机智能视频监控的破局之道
在突发事件、高空巡查、边远区域布控中,传统摄像头常常“看不到、跟不上、调不动”。无人机智能视频监控系统,打破地面视角局限,以“高空布控 AI分析 实时响应”赋能政企单位智能化管理。在城市应急指挥中心的大屏上,一场暴雨正…...

声音信号的基频检测(python版本)
import math import wave import array import functools from abc import ABC, abstractmethod import matplotlib import matplotlib.pyplot as plt from matplotlib.gridspec import GridSpec import os import sys# 设计模式部分 class PreprocessStrategy(ABC):"&q…...

STM32 控制12VRGB灯带颜色亮度调节,TFTLCD显示
接了一个同学的小项目,要实现控制一个实体,控制灯带的亮度为红/绿/蓝/白/黄以及亮度的叠加。 时间要的比较急,要两天实现,因此不能打板,只能采用现有模块拼接。 一. 实施方案 一开始觉得很简单,就是使用五…...
Hive开窗函数的进阶SQL案例
一、开窗函数基础 1. 定义与作用 开窗函数(Window Functions)在保留原始行数据的同时,对分组内的行进行聚合或排序分析,常用于累计计算、排名、移动平均等场景。与普通聚合函数(如SUM、AVG)的区别…...

【JJ斗地主-注册安全分析报告】
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞 …...

《绩效管理》要点总结与分享
目录 绩效管理与目标设定 绩效管理的循环:PDCA 绩效目标的设定要点 绩效设定的工具:SMART法则 绩效跟踪与评估 刻板印象:STAR法 晕轮效应:对比评价法 近因效应:关键事项评估表 绩效面谈 面谈前准备工作 汉堡…...

Microsoft前后端不分离编程新风向:cshtml
文章目录 什么是CSHTML?基础语法内联表达式代码块控制结构 布局页面_ViewStart.cshtml_Layout.cshtml使用布局 模型绑定强类型视图模型集合 HTML辅助方法基本表单验证 局部视图创建局部视图使用局部视图 高级特性视图组件依赖注入Tag Helpers 性能优化缓存捆绑和压缩…...

【评测】用Flux的图片文本修改的PS效果
【评测】Flux的图片文本修改的PS效果 1. 百度图库找一张有英文的图片 2. 打开https://playground.bfl.ai/image/edit上传图片 3. 输入提示词 “change brarfant to goodbeer” 图片的文字被修改了...
青少年编程与数学 01-011 系统软件简介 01 MS-DOS操作系统
青少年编程与数学 01-011 系统软件简介 01 MS-DOS操作系统 1. MS-DOS的历史背景1.1 诞生背景1.2 发展历程1.3 与Windows的关系 2. MS-DOS的技术细节2.1 系统架构2.2 启动过程2.3 内存管理2.4 设备驱动程序 3. MS-DOS的用户界面3.1 命令行界面3.2 配置文件 4. MS-DOS的应用程序与…...

数据库管理-第334期 Oracle Database 23ai测试版RAC部署文档(20250607)
数据库管理334期 2024-06-07 数据库管理-第334期 Oracle Database 23ai测试版RAC部署文档(20240607)1 环境与安装介质2 操作标准系统配置2.1 关闭防火墙2.2 关闭SELinux2.3 关闭avahi-daemon2.4 时间同步配置 3 存储服务器配置3.1 配置本地yum源3.2 安装…...
springCloud2025+springBoot3.5.0+Nacos集成redis从nacos拉配置起服务
文章目录 前言一、网关gateway选型1. 响应式编程模型2. 网关的特定需求3. 技术栈一致性4. 性能对比5. 实际应用场景优势 二、redis的集成1.引入库2.配置类A、自定义配置类RedisAfterNacosAutoConfigurationB、自定义配置类RedisConfig 总结 前言 最近在搭建最新的springCloud …...

AI生成的基于html+marked.js实现的Markdown转html工具,离线使用,可实时预览 [
有一个markdown格式的文档,手头只有notepad的MarkdownPanel插件可以预览,但是只能预览,不能直接转换为html文件下载,直接复制预览的内效果又不太好,度娘也能找到很多工具,但是都需要在线使用。所以考虑用AI…...

机器学习:load_predict_project
本文目录: 一、project目录二、utils里的两个工具包(一)common.py(二)log.py 三、src文件夹代码(一)模型训练(train.py)(二)模型预测(…...
OkHttp 3.0源码解析:从设计理念到核心实现
本文通过深入分析OkHttp 3.0源码,揭示其高效HTTP客户端的实现奥秘,包含核心设计理念、关键组件解析、完整工作流程及实用技巧。 一、引言:为什么选择OkHttp? 在Android和Java生态中,OkHttp已成为HTTP客户端的标准选择…...

【storage】
文章目录 1、RAM and ROM2、DRAM and SRAM2、Flash Memory(闪存)4、DDR and SPI NOR Flash5、eMMC6、SPI NOR vs SPI NAND vs eMMC vs SD附录——prototype and demo board附录——U盘、SD卡、TF卡、SSD参考 1、RAM and ROM RAM(Random Acce…...
微信小程序带参分享、链接功能
分享链接的功能是右上角点...然后复制链接,可以直接点击 #小程序://**商城/p5XqHti******* 这种链接直接从其他地方跳转到小程序 wx.onCopyUrl(() > {return {query: "shareCode" this.shareCode,}; }); query就是参数,直接在onload里…...

JVM 垃圾回收器 详解
垃圾收集器 SerialSerial Old:单线程回收,适用于单核CPU场景ParNewCMS:暂停时间较短,适用于大型互联网应用中与用户交互的部分Paraller ScavengeParallel Old:吞吐量高,适用于后台进行大量数据操作G1&#…...

FreeRTOS任务之深入篇
目录 1.Tick1.1 Tick的概念1.2 Tick与任务调度1.3 Tick与延时函数 2.任务状态2.1 运行状态 (Running)2.2 就绪状态 (Ready)2.3 阻塞状态 (Blocked)5.4 暂停状态 (Suspended)2.5 特殊状态:删除状态 (Deleted)5.6 任务状态转换2.7 实验 3.Delay函数3.1 两个函数3.2 实…...

Linux 系统、代码与服务器进阶知识深度解析
在数字化时代,Linux 系统凭借其开源、稳定、安全的特性,成为服务器领域和软件开发的核心支柱。除了算法优化技巧,Linux 系统在网络服务、容器化技术、服务器安全等方面也蕴含着丰富的知识和实用技术。接下来,我们将深入探讨这些领…...

人工智能--AI换脸
本文实现了一个简易的人脸交换程序,主要功能包括:1)检查所需的模型文件是否存在;2)使用预训练的Caffe模型检测图像中的人脸;3)将源图像的人脸区域通过泊松融合无缝地替换到目标图像上。程序通过OpenCV的DNN模块加载人脸检测模型&a…...

NLP学习路线图(二十七):Transformer编码器/解码器
一、Transformer概览:抛弃循环,拥抱注意力 传统RNN及其变体(如LSTM、GRU)处理序列数据时存在顺序依赖的瓶颈:必须逐个处理序列元素,难以并行计算,且对长程依赖建模能力较弱。Transformer的革命…...

【机器学习】支持向量机实验报告——基于SVM进行分类预测
目录 一、实验题目描述 二、实验步骤 三、Python代码实现基于SVM进行分类预测 四、我的收获 五、我的感受 一、实验题目描述 实验题目:基于SVM进行分类预测 实验要求:通过给定数据,使用支持向量机算法(SVM)实现分…...
策略模式实战:Spring中动态选择商品处理策略的实现
概念 可以在运行时期动态的选择需要的具体策略类,处理具体的问题 组成元素 策略接口 public interface GoodsStrategy {void handleGoods(); } 具体策略类 Service(Constants.BEAN_GOODS) public class BeanGoodsStrategy implements GoodsStrategy {Override…...
主流信创数据库对向量功能的支持对比
主流信创数据库对向量功能的支持对比 版本支持对比向量索引支持对比距离函数支持对比使用限制对比OceanBase向量数据库GaussDB向量数据库TiDB向量数据库VastBase向量数据库 ⭐️ 本文章引用数据截止于2025年5月31日。 版本支持对比 数据库产品支持向量功能的版本OceanBaseOce…...
Matlab | matlab中的画图工具详解
二维图形到高级三维可视化 **一、基础二维绘图****二、三维可视化****三、图形修饰工具****四、高级功能****五、交互式工具****六、面向对象绘图(推荐)****七、常用技巧****学习资源**在MATLAB中,画图工具(绘图功能)是其核心优势之一,涵盖从基础二维图形到高级三维可视化…...

HA: Wordy靶场
HA: Wordy 来自 <HA: Wordy ~ VulnHub> 1,将两台虚拟机网络连接都改为NAT模式 2,攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 那么攻击机IP为192.168.23.128,靶场IP192.168.23.130 3,对靶机进行端口服务探…...
6.7本日总结
一、英语 复习默写list10list19,07年第3篇阅读 二、数学 学习线代第一讲,写15讲课后题 三、408 学习计组第二章,写计组习题 四、总结 本周结束线代第一讲和计组第二章,之后学习计网4.4,学完计网4.4之后开操作系…...