时间复杂度_空间复杂度

时间复杂度_空间复杂度

1.算法效率

算法效率分析分为两种:第一种是时间效率,第二种是空间效率

时间效率被称为时间复杂度,而空间效率被称作空间复杂度。时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间。

在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度

2.时间复杂度

2.1时间复杂度的概念

时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。

一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。

2.2大O的渐进表示法

// 请计算一下Func1 基本操作执行了多少次?
void Func1(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);
}int main()
{Func1(10);// 130Func1(100);// 10210Func1(1000);// 1002010// 我们发现该函数需要执行 N^2 + 2*N + 10 次// 那么这个函数的时间复杂度应该是  N^2 + 2*N + 10 次 才对// 而我们使用大O阶方法,该函数的时间复杂度为 O(N^2) return 0;
}

实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大0的渐进表示法。

大O符号 (Big O notation): 是用于描述函数渐进行为的数学符号

推导大O阶方法:

  1. 用常数1取代运行时间中的所有加法常数
  2. 在修改后的运行次数函数中,只保留最高阶项
  3. 如果最高阶项存在且不是1,则去除与这个项目相乘的常数,得到的结果就是大O阶。

使用大O的渐进表示法以后,Func1的时间复杂度为:O(N^2)

通过上面我们会发现大0的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。

另外有些算法的时间复杂度存在最好、平均和最坏情况

最坏情况:任意输入规模的最大运行次数(上界)

平均情况:任意输入规模的期望运行次数

最好情况:任意输入规模的最小运行次数(下界)

例如: 在一个长度为N数组中搜索一个数据X

最好情况:1次找到

最坏情况:N次找到

平均情况:N/2次找到

在实际中,一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)

2.3 常见时间复杂度计算例子

第一个:
// 请计算Func2的时间复杂度
void Func2(int N)
{int count = 0;for (int k = 0; k < 2 * N; ++k) // 这里的2*N的2会被去掉,因为影响不大{++count;}int M = 10;while (M--){++count;}printf("%d\n", count);
}
// 时间复杂度为O(N)
第二个:
// 计算Func3的时间复杂度 
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);
}
// 时间复杂度为 O(N+M) 
// // 如果N远大于M  那就是O(N)  反之则是O(M)
第三题:
// 计算Func4的时间复杂度 
void Func4(int N)
{int count = 0;for (int k = 0; k < 100; ++k){++count;}printf("%d\n", count);
}
//  时间复杂度为O(1)
第四题:
// 计算strchr的时间复杂度?
const char* strchr(const char* str, int character);
// 时间复杂度为O(N) 因为尽管我们只调用了一次,但是里面的运算 也是算在时间复杂度里面的
第五题:
// 计算Bubblesort的时间复杂度? [冒泡排序]
void BubbleSort(int* a, int n)
{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;}
}
// 准确的时间复杂度是:
// F(N) = 1 + 2 + ... +n-1 的等差数列计算
// 时间复杂度O(N^2)
// 最好的情况O(N)
第六题:
// 计算BinarySearch的时间复杂度?
int BinarySearch(int* a, int n, int x)
{int begin = 0;int end = n;while (begin < end){int mid = begin + ((end - begin) >> 1);if (a[mid] < x)begin = mid + 1;else if (a[mid] > x)end = mid;elsereturn mid;}return -1;
}
// 时间复杂度是O(log2^N)  最好情况是O(1)
// 因为二分查找,每次找都是除于2。 要找到一个数,X是查找的次数  2的X次方 = N 
// 那我们去反向查找这个 X 就是log2 N  【以2为底】
第七题:
// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{if (0 == N)return 1;return Fac(N - 1) * N;
}// 时间复杂度为 O(N)   如果这个有循环调用了N次递归,那么时间复杂度为O(N^2)
第八题:
// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{if (N < 3)return 1;return Fib(N - 1) + Fib(N - 2);
}
// 大体上一共要调用 2^0 + 2^1 + 2^2 + ... +2^(n - 1) = 2^n - 1  次  
// 实际上总有递归先结束调用 比如Fib(N - 2)就会比Fib(N - 1)先结束调用 
// 那么实际上的调用次数并没有 2^n - 1 次 那么多,但是影响微乎其微 
// 所以时间复杂度为 O(2^n) 

如图所示:

image-20240429104156382

实际上这个O(2^N)这个时间复杂度 是非常慢的。

那我们要怎么样去优化这个代码呢?

我们知道 时间复杂度太大的原因是 进行了太多次的重复计算,那我们就让这个重复计算变少不就行了。

我们看下面的代码:

// 优化
long long* Fib_r(size_t N)
{// 我们知道 上面代码之所以时间复杂度如此大 是因为进行了太多次重复的运算// 创建一个数组,把每一次计算的数 都存起来 这样的话我们就可以避免进行重复的运算long long* fibArray = (long long*)malloc(sizeof(long long) * (N + 1));fibArray[0] = 0;if (N == 0) return	NULL; fibArray[1] = 1;// 以空间换时间for (int i = 2; i <= N; i++){fibArray[i] = fibArray[i - 1] + fibArray[i - 2];}return fibArray;
}
// 在这种情况下 我们经过优化的时间复杂度就是 O(N)int main()
{long long* result = Fib_r(1000);printf("%lld\n", result[1000]);  // 817770325994397771return 0;
}

经过优化后 时间复杂度降低 为 O(N)。

实例答案及分析:
  1. 实例1基本操作执行了2N+10次,通过推导大O阶方法知道,时间复杂度为 O(N)

  2. 实例2基本操作执行了M+N次,有两个未知数M和N,时间复杂度为 O(N+M)

  3. 实例3基本操作执行了10次,通过推导大O阶方法,时间复杂度为 O(1)

  4. 实例4基本操作执行最好1次,最坏N次,时间复杂度一般看最坏,时间复杂度为 O(N)

  5. 实例5基本操作执行最好N次,最坏执行了(N*(N+1)/2次,通过推导大O阶方法+时间复杂度一般看最坏,时间复杂度为 O(N^2)

  6. 实例6基本操作执行最好1次,最坏O(logN)次,时间复杂度为 O(logN) ps:logN在算法分析中表示是底数为2,对数为N。有些地方会写成lgN。(建议通过折纸查找的方式讲解logN是怎么计算出来的)

  7. 实例7通过计算分析发现基本操作递归了N次,时间复杂度为O(N)。

  8. 实例8通过计算分析发现基本操作递归了2N次,时间复杂度为O(2N)。(建议画图递归栈帧的二叉树讲解)

3.空间复杂度

前面我们说了,时间复杂度不计算时间,计算大概的运算次数。

同样的,空间复杂度不计算空间,计算的大概定义的变量个数。

空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度

空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。(因为现代科技下,空间过于庞大,对于GB这个单位来说。占用多少字节不在那么重要了)

空间复杂度计算规则基本跟实践复杂度类似,也使用O渐进表示法

注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。

常见空间复杂度计算例子:

实例1:

// 计算BubbleSort的空间复杂度?
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;}
}
// 空间复杂度是O(1) O(1)不是1个 而是代表创建的变量是常数个

实例2:

// 计算Fibonacci的空间复杂度?
// 返回斐波那契数列的前n项
long long* Fibonacci(size_t n)
{if (n == 0)return NULL;long long* fibArray = (long long*)malloc((n + 1) * sizeof(long long));fibArray[0] = 0;fibArray[1] = 1;for (int i = 2; i <= n; ++i){fibArray[i] = fibArray[i - 1] + fibArray[i - 2];}return fibArray;
}
// 开辟了n+1的变量   空间复杂度为O(n)  

实例3:

// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{if (N == 0)return 1;return Fac(N - 1) * N;
}
// 每次递归都会开辟一个函数栈帧,这个栈帧里面会创建许多变量 (如寄存器等等)
// 每个栈帧里面的创建变量都是常数   n 次递归  
// 空间复杂度为 O(N) 

再来看一个拓展:

// 计算斐波那契递归Fib的空间复杂度?
long long Fib(size_t N)
{if (N < 3)return 1;return Fib(N - 1) + Fib(N - 2);
}

前面我们经过学习和计算,我们知道了该代码的时间复杂度是O(2^N)。

那我们也可以来解析一下空间复杂度是多少。

首先,我们知道,该函数大概会调用 2^N - 1 次, 实际上比这个次数少,但是大体是这个次数,而我们又知道每次调用函数都会创建函数栈帧,在栈帧中我们会创建常数个变量,也就是调用一次函数的空间复杂度是O(1),而我们这里大概会调用2^N - 1 次,因此该函数的空间复杂度就是O(2^N)。

// 每个栈帧里面的创建变量都是常数 n 次递归
// 空间复杂度为 O(N)


再来看一个拓展:```c
// 计算斐波那契递归Fib的空间复杂度?
long long Fib(size_t N)
{if (N < 3)return 1;return Fib(N - 1) + Fib(N - 2);
}

前面我们经过学习和计算,我们知道了该代码的时间复杂度是O(2^N)。

那我们也可以来解析一下空间复杂度是多少。

首先,我们知道,该函数大概会调用 2^N - 1 次, 实际上比这个次数少,但是大体是这个次数,而我们又知道每次调用函数都会创建函数栈帧,在栈帧中我们会创建常数个变量,也就是调用一次函数的空间复杂度是O(1),而我们这里大概会调用2^N - 1 次,因此该函数的空间复杂度就是O(2^N)。

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

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

相关文章

C#技巧之同步与异步

区别 首先&#xff0c;同步就是程序从上往下顺序执行&#xff0c;要执行完当前流程&#xff0c;才能往下个流程去。 而异步&#xff0c;则是启动当前流程以后&#xff0c;不需要等待流程完成&#xff0c;立刻就去执行下一个流程。 同步示例 创建一个窗体&#xff0c;往窗体里…

2131 - 枚举-练习-涂国旗

2131 - 枚举-练习-涂国旗 c刷题 超能力编程 分析 枚举涂w的底边和涂b的底边即可 剩下的部分都涂r 数据范围这么小,暴力枚举&#xff0c;代码简单难度低。搜索什么的用不着啦&#xff01; 那么问题来了&#xff1a;怎么枚举呢&#xff1f; 我们只要枚举白与蓝、蓝与红的边界&…

【DPU系列之】DPU中的ECPF概念是什么?全称是什么?(E CPF对标H CPF;embedded CPU function ownership)

ECPF&#xff1a;embedded CPU function ownership。 嵌入式CPU运转ownership。也叫DPU模式&#xff0c;是DPU工作运转3种模式之一&#xff0c;也是默认的模式。这里的嵌入式CPU指的是DPU上ARM CPU&#xff0c;表示网卡所有资源和功能被embedded CPU全权管理&#xff0c;行使所…

【动态规划】投资问题

本文利用markdown基于https://blog.csdn.net/qq_41926985/article/details/105627049重写,代码部分为本人编辑 代码要求 应用动态规划方法&#xff0c;求解投资问题&#xff0c;实现下面的例子。 #define MAX_N 4 //最大投资项目数目 #define MAX_M 5 //最大投资钱数(万元) /…

【机器视觉】yolo-world-opencvsharp-.net4.8 C# 窗体应用程序

这段代码是基于 OpenCvSharp, OpenVinoSharp 和 .NET Framework 4.8 的 Windows Forms 应用程序。其主要目的是加载和编译机器学习模型&#xff0c;对输入数据进行推理&#xff0c;并显示结果。 下面是该程序的主要功能和方法的详细总结&#xff1a; 初始化 OpenVINO 运行时核心…

基于Pytorch深度学习——卷积神经网络(卷积层/池化层/多输入多输出通道/填充和步幅/)

本文章来源于对李沐动手深度学习代码以及原理的理解&#xff0c;并且由于李沐老师的代码能力很强&#xff0c;以及视频中讲解代码的部分较少&#xff0c;所以这里将代码进行尽量逐行详细解释 并且由于pytorch的语法有些小伙伴可能并不熟悉&#xff0c;所以我们会采用逐行解释小…

【DPU系列之】如何通过带外口登录到DPU上的ARM服务器?(Bluefield2举例)

文章目录 1. 背景说明2. 详细操作步骤2.1 目标拓扑结构2.2 连接DPU带外口网线&#xff0c;并获取IP地址2.3 ssh登录到DPU 3. 进一步看看系统的一些信息3.1 CPU信息&#xff1a;8核A723.2 内存信息 16GB3.3 查看ibdev设备 3.4 使用小工具pcie2netdev查看信息3.5 查看PCIe设备信息…

python笔记:gensim进行LDA

理论部分&#xff1a;NLP 笔记&#xff1a;Latent Dirichlet Allocation &#xff08;介绍篇&#xff09;-CSDN博客 参考内容&#xff1a;DengYangyong/LDA_gensim: 用gensim训练LDA模型&#xff0c;进行新闻文本主题分析 (github.com) 1 导入库 import jieba,os,re from ge…

【云原生】Docker 的网络通信

Docker 的网络通信 1.Docker 容器网络通信的基本原理1.1 查看 Docker 容器网络1.2 宿主机与 Docker 容器建立网络通信的过程 2.使用命令查看 Docker 的网络配置信息3.Docker 的 4 种网络通信模式3.1 bridge 模式3.2 host 模式3.3 container 模式3.4 none 模式 4.容器间的通信4.…

Stream流操作

看到Stream流这个概念&#xff0c;我们很容易将其于IO流联系在一起&#xff0c;事实上&#xff0c;两者并没有什么关系&#xff0c;IO流是用于处理数据传输的&#xff0c;而Stream流则是用于操作集合的。 当然&#xff0c;为了方便我们区分&#xff0c;我们依旧在这里复习一下…

长期找 AI 专家,邀请参加线上聊天直播

诚邀 AI 专家参加线上聊天&#xff0c;成为嘉宾。 分享前沿观点、探讨科技和生活 除节假日外&#xff0c;每周举办在线聊天直播 根据话题和自愿形式结合&#xff0c;每期 2~3 位嘉宾 成为嘉宾&#xff0c;见下&#xff1a;

ADS软件(PathWave 先进设计系统软件)分享与安装

ADS软件的简介 ADS软件&#xff08;Advanced Design System&#xff09;主要用于射频&#xff08;RF&#xff09;、微波&#xff08;Microwave&#xff09;和毫米波&#xff08;Millimeter-wave&#xff09;电路的设计、仿真和分析。它提供了一套强大的工具和功能&#xff0c;…

Angular进阶-NVM管理Node.js实现不同版本Angular环境切换

一、NVM介绍 1. NVM简介 Node Version Manager&#xff08;NVM&#xff09;是一个用于管理多个Node.js版本的工具。它允许用户在同一台机器上安装和使用多个Node.js版本&#xff0c;非常适合需要同时进行多个项目的开发者。NVM是开源的&#xff0c;支持MacOS、Windows和Linux…

【解决】docker一键部署报错

项目场景见&#xff1a;【记录】Springboot项目集成docker实现一键部署-CSDN博客 问题&#xff1a; 1.docker images 有tag为none的镜像存在。 2.有同事反馈&#xff0c;第一次启动docker-compose up -d 项目无法正常启动。后续正常。 原因&#xff1a; 1.服务中指定了镜像m…

Jackson-jr 对比 Jackson

关于Jackson-jr 对比 Jackson 的内容&#xff0c;有人在做了一张下面的图。 简单点来说就 Jackson-jr 是Jackson 的轻量级应用&#xff0c;因为我们在很多时候都用不到 Jackson 的很多复杂功能。 对很多应用来说&#xff0c;我们可能只需要使用简单的 JSON 读写即可。 如我们…

【Linux网络】网络文件共享

目录 一、存储类型 二、FTP文件传输协议 2.1 FTP工作原理 2.2 FTP用户类型 2.3 FTP软件使用 2.3.1 服务端软件vsftpd 2.3.2 客户端软件ftp 2.4 FTP的应用 2.4.1 修改端口号 2.4.2 匿名用户的权限 2.4.3 传输速率 三、NFS 3.1 工作原理 3.2 NFS软件介绍 3.3 NFS配…

企业级数据治理学习总结

1. 水在前面 “数据治理”绝对是吹过的牛里面最高大上的题目了&#xff0c;本来想直接以《企业级数据治理》为题来水的&#xff0c;码字前又跑去图书馆借了几本书&#xff0c;翻了几页才发现自己连半桶水都提不起&#xff0c;撑死只能在小屁孩跟前吹吹牛。 好吧&#xff0c;实在…

怎么把jpg图片变成gif?参考这个方法一键制作

Jpg图片如何变成gif图片&#xff1f;Jpg、gif都是最常用的图片格式&#xff0c;想要将这两种格式的图片互相转化的时候要怎么操作呢&#xff1f;想要将jpg图片变成gif方法很简单&#xff0c;只需要使用gif图片制作&#xff08;https://www.gif5.net/&#xff09;工具-GIF5工具网…

华为手机ip地址怎么切换

随着移动互联网的普及&#xff0c;IP地址成为了我们手机上网的重要标识。然而&#xff0c;在某些情况下&#xff0c;我们可能需要切换手机的IP地址&#xff0c;以更好地保护个人隐私、访问特定地区的内容或服务&#xff0c;或者出于其他网络需求。华为手机作为市场上的热门品牌…

用队列实现栈——leetcode刷题

题目的要求是用两个队列实现栈&#xff0c;首先我们要考虑队列的特点&#xff1a;先入先出&#xff0c;栈的特点&#xff1a;后入先出&#xff0c;所以我们的目标就是如何让先入栈的成员后出栈&#xff0c;后入栈的成员先出栈。 因为有两个队列&#xff0c;于是我们可以这样想&…