【数据结构】复杂度详解


目录

(一)算法的复杂度

(二)时间复杂度

(1)练笔+解释:

i,示例1

ii,示例2

iii,二分查找

 iv,斐波那契

(三)空间复杂度

 练笔+解释:

i,冒泡排序

ii,斐波那契

(四)常见复杂度对比:


正文开始:

        我们为什么要讨论复杂度呢?因为复杂度能够衡量一个程序算法的好坏,关乎你写的程序能否在你的这台计算机上执行,如果能够执行,执行的效率又怎么样?如果程序的空间复杂度太大,可能根本无法在计算机上执行,因为计算机没有足够大的空间;如果时间复杂度太大,那么在有限的时间内可能根本没办法得到答案;因此,讨论复杂度是必要的。

         算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。

(一)算法的复杂度

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


(二)时间复杂度

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

        时间复杂度并不是表示一个程序解决问题需要花多少时间,而是当问题规模扩大后,程序需要的时间长度增长得有多快。也就是说,对于高速处理数据的计算机来说,处理某一个特定数据的效率不能衡量一个程序的好坏,而应该看当这个数据的规模变大到数百倍后,程序运行时间是否还是一样,或者也跟着慢了数百倍,或者变慢了数万倍。(来自 <什么是P问题、NP问题和NPC问题 | Matrix67: The Aha Moments>)

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

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


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

大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。
有些算法的时间复杂度存在最好、平均和最坏情况:


        最坏情况:任意输入规模的最大运行次数(上界)
        平均情况:任意输入规模的期望运行次数
        最好情况:任意输入规模的最小运行次数(下界)


        在实际中一般情况关注的是算法的最坏运行情况。

 

(1)练笔+解释:

i,示例1

//在这个函数中,count++一共被执行了多少次?
void Fun_example1(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);
}

         在Fun_example1函数中,count++语句一共被执行了F(N)=N^2+2*N+10次;随着N的增大,N^2对时间复杂度的影响逐渐凸显,因此N^2是最高阶项,保留最高结项得到Fun_example1的时间复杂度为O(N^2)。


ii,示例2

//这个函数呢,count++被执行了多少次?
void Func_example2(int N)
{int count = 0;for (int k = 0; k < 2 * N ; ++ k){++count;}int M = 10;while (M--){++count;}printf("%d\n", count);
}

        在Fun_example2函数中,count++语句一共被执行了F(N)=2*N+10次;随着N的增大,2*N对时间复杂度的影响逐渐凸显,因此2*N是最高阶项,保留最高阶项并去掉最高阶项的系数得到Fun_example1的时间复杂度为O(N)。


iii,二分查找

//二分查找的时间复杂度是多少?
int BinarySearch(int* a, int n, int x)
{assert(a);int begin = 0;int end = n-1;// [begin, end]:begin和end是左闭右闭区间,因此有=号while (begin <= end){int mid = begin + ((end-begin)>>1);if (a[mid] < x)begin = mid+1;else if (a[mid] > x)end = mid-1;elsereturn mid;}return -1;
}

        二分查找的基本操作最好执行一次,最坏执行O(logN)次,时间复杂度按照最坏的算,是O(logN)。(在算法分析中,如果没有特殊说明,logN表示以二为底N的对数)


 iv,斐波那契

 斐波那契

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

 当n=3时,递归展开图如图:

 当n=4,递归展开图如图:

        

当n=5,递归展开图如图: 

         我在作图的时候其实是挺方便的,做n=5的图只需将n=4与n=3的图放在下边即可,这就是复用。但是计算机在计算的时候不会像我这样复用已经计算出来的结果,对于递归的每一层,计算机都会从最开始的第一层算到这一层。换句话说:我可以n的值为3和4的图链接起来,形成n=5的图;计算机在计算n=5的递归值时,计算机不会将n的值为3和4的递归结果加起来得到5,而是从n=1或2算到n=3的结果,在同样算到n=4的结果,在这之后再相加得到n=5的值。

(这预示着斐波那契递归算法的效率是极低的!)

随着n的增大:

递归树越来越接近满二叉树,它比满二叉树缺少了一部分:

         假设递归树的高度为h,那么整个递归过程的时间复杂度可以近似表示为每一层的节点数乘以高度h。因此,时间复杂度可以表示为:

T(n) = O(2^0) + O(2^1) + O(2^2) + ... + O(2^h)

        由于每一层的节点数是逐渐减少的,可以将上述等式转化为以下形式:

T(n) ≤ O(2^0) + O(2^1) + O(2^2) + ... + O(2^(h-1)) + O(2^h)

(你也可以对等比数列求和)

        我们知道,对于任意正整数k,2^k ≥ k。因此,上述等式可以进一步简化为:

T(n) ≤ O(h2^h)

        由于递归树的高度h与输入规模n之间存在一个关系h = O(log n),因此可以将上述等式进一步简化为:

T(n) ≤ O(log n * 2^log n) = O(n log n)

所以,使用递归方式求解斐波那契数列的时间复杂度可以表示为O(n log n)。


(三)空间复杂度

        空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法。

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


空间复杂度与时间复杂度不同的是:

        空间的销毁是归还使用权,操作系统仍然可以使用,因此时间是逐渐积累的,是一去不复返的;而空间是可以重复利用的。

 练笔+解释:

i,冒泡排序

// 计算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)。


ii,斐波那契

// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{if(N == 0)return 1;return Fac(N-1)*N;
}

        递归调用了N次,开辟每个栈帧开辟了常数个空间,空间复杂度为O(N)。


        而斐波那契的空间是动态的,既有开辟又有释放,按照最大的占用空间算,空间复杂度为O(N)。

(四)常见复杂度对比:

 


完~

未经作者同意禁止转载

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

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

相关文章

英福康INFICON真空计MPG400MPG401使用说明PPT讲解课件

英福康INFICON真空计MPG400MPG401使用说明PPT讲解课件

Java解决杨辉三角

Java解决杨辉三角 01 题目 给定一个非负整数 *numRows&#xff0c;*生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]示例 2: 输入: numRo…

计网面试题整理下

1. HTTP常见的状态码有哪些&#xff1f; 常见状态码&#xff1a; 200&#xff1a;服务器已成功处理了请求。 通常&#xff0c;这表示服务器提供了请求的网页。301 &#xff1a; (永久移动) 请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时&am…

前端面试 跨域理解

2 实现 2-1 JSONP 实现 2-2 nginx 配置 2-2 vue 开发中 webpack自带跨域 2 -3 下载CORS 插件 或 chrome浏览器配置跨域 2-4 通过iframe 如&#xff1a;aaa.com 中读取bbb.com的localStorage 1)在aaa.com的页面中&#xff0c;在页面中嵌入一个src为bbb.com的iframe&#x…

在全志V853平台上成功部署深度学习步态识别算法

北理工通信课题组辛喆同学在本科毕业设计《基于嵌入式系统的步态识别的研究》中&#xff0c;成功将深度步态识别算法GaitSet移植到全志V853开发板上。本研究在CASIA-B数据集上进行测试&#xff0c;正常行走状态下该系统的步态识别准确率达到了94.9%&#xff0c;背包行走和穿外套…

C++基于多设计模式下的同步异步日志系统day5

C基于多设计模式下的同步&异步日志系统day5 &#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C基于多设计模式下的同步&异步日志系统 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&am…

C++:Vector的模拟实现

创作不易&#xff0c;感谢三连 &#xff01;&#xff01; 一&#xff0c;前言 在学习string类的时候&#xff0c;我们可能会发现遍历的话下标访问特别香&#xff0c;比迭代器用的舒服&#xff0c;但是下标其实只能是支持连续的空间&#xff0c;他的使用是非常具有局限性的&am…

第 125 场 LeetCode 双周赛题解

A 超过阈值的最少操作数 I 排序然后查找第一个大于等于 k 的元素所在的位置 class Solution { public:int minOperations(vector<int> &nums, int k) {sort(nums.begin(), nums.end());return lower_bound(nums.begin(), nums.end(), k) - nums.begin();} };B 超过阈…

C++ 位运算OJ

目录 1、 191. 位1的个数 2、 338. 比特位计数 3、 461. 汉明距离 4、136. 只出现一次的数字 5、 260. 只出现一次的数字 III 6、面试题 01.01. 判定字符是否唯一 7、 268. 丢失的数字 8、 371. 两整数之和 9、 137. 只出现一次的数字 II 10、面试题 17.19. 消失的两个…

【C++】二叉树进阶面试题(上)

目录 1. 二叉树创建字符串 题目 分析 代码 2. 二叉树的分层遍历1 题目 分析 代码 3. 二叉树的分层遍历2 题目 分析 代码 4. 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先 题目 分析 代码 5. 二叉树搜索树转换成排序双向链表 题目 分析 代码 1. …

使用GitHub API 查询开源项目信息

一、GitHub API介绍 GitHub API 是一组 RESTful API 接口&#xff0c;用于与 GitHub 平台进行交互。通过使用 GitHub API&#xff0c;开发人员可以访问和操作 GitHub 平台上的各种资源&#xff0c;如仓库、提交记录、问题等。 GitHub API 提供了多种功能和端点&#xff0c;以…

昇腾芯片解析:华为自主研发的人工智能处理器全面分析

在当今科技发展的浪潮中&#xff0c;昇腾芯片作为一种新兴的处理器&#xff0c;正引起广泛的关注和讨论。升腾芯片究竟是由哪家公司生产的&#xff1f;这个问题一直困扰着许多人。下面小编将全面介绍、分析升腾芯片的生产商及各类参数、应用&#xff0c;以便读者对其有更全面的…

自学高效备考2025年AMC8数学竞赛:2000-2024年AMC8真题解析

今天继续来随机看五道AMC8的真题和解析&#xff0c;根据实践经验&#xff0c;对于想了解或者加AMC8美国数学竞赛的孩子来说&#xff0c;吃透AMC8历年真题是备考最科学、最有效的方法之一。即使不参加AMC8竞赛&#xff0c;吃透了历年真题600道和背后的知识体系&#xff0c;那么小…

Maven终端命令生成Spring-boot项目并输出“helloworld“

1. 生成项目 mvn archetype:generate填写groupId和artifactId&#xff0c;其余默认即可 2. 修改pom.xml文件 将如下内容放入pom.xml文件内 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artif…

Conda笔记--移动Conda环境后pip使用异常的解决

1--概述 由于各种原因&#xff0c;需要将Anaconda转变为Minicoda&#xff0c;为了保留之前安装的所有环境&#xff0c;直接将anaconda3/envs的所有环境拷贝到Miniconda/envs中&#xff0c;但在使用移动后环境时会出现pip的错误&#xff1a;bad interpreter: No such file or di…

计算机网络-物理层-传输媒体

传输媒体的分类 导向型-同轴电缆 导向型-双绞线 导向型-光纤 非导向型

面试问答总结之Java进阶

文章目录 &#x1f412;个人主页&#x1f3c5;JavaEE系列专栏&#x1f4d6;前言&#xff1a;&#x1f380;注解Annotaion &#xff08;java标注&#xff09;&#x1f415;内置注解&#x1f415;元注解 &#x1f380;对象克隆&#x1f415;如何实现克隆&#x1f415;如何实现深克…

C#,最小代价多边形三角剖分MCPT(Minimum Cost Polygon Triangulation)算法与源代码

1 最小代价多边形三角剖分算法 凸多边形的三角剖分是通过在非相邻顶点&#xff08;角点&#xff09;之间绘制对角线来形成的&#xff0c;这样对角线就不会相交。问题是如何以最小的代价找到三角剖分的代价。三角剖分的代价是其组成三角形的权重之和。每个三角形的重量是其周长…

LLM春招准备(1)

llm排序 GPT4V GPT-4V可以很好地理解直接绘制在图像上的视觉指示。它可以直接识别叠加在图像上的不同类型的视觉标记作为指针&#xff0c;例如圆形、方框和手绘&#xff08;见下图&#xff09;。虽然GPT-4V能够直接理解坐标&#xff0c;但相比于仅文本坐标&#xff0c;GPT-4V在…

蓝桥杯练习题——dp

五部曲&#xff08;代码随想录&#xff09; 1.确定 dp 数组以及下标含义 2.确定递推公式 3.确定 dp 数组初始化 4.确定遍历顺序 5.debug 入门题 1.斐波那契数 思路 1.f[i]&#xff1a;第 i 个数的值 2.f[i] f[i - 1] f[i - 2] 3.f[0] 0, f[1] 1 4.顺序遍历 5.记得特判 …