【数据结构与算法】算法优化、时间复杂度、空间复杂度

文章目录

  • 一、什么是复杂度?
  • 二、大O表示法
  • 三、时间复杂度计算
  • 四、常见复杂度的比较
  • 五、算法优化的核心方法论
  • 六、常见算法复杂度
  • 五、总结


一、什么是复杂度?

复杂度是衡量代码运行效率的重要的度量因素。 而复杂度主要就是指时间复杂度和空间复杂度

首先,时间复杂度的计算并不是计算程序具体运行的时间,而是算法执行语句的次数。当我们面前有多个算法时,我们可以通过计算时间复杂度,判断出哪一个算法在具体执行时花费时间最多和最少。
空间复杂度则是对一个算法在运行过程中临时占用电脑存储空间大小的量度。

在过去,由于计算机性能低下,系统存储空间很小,这种情况下,降低空间的损耗就极其重要了!所以,在这种情况下,程序员要考虑的就是尽可能的降低空间复杂度。

但是,时间复杂度和空间复杂度往往是相对的。降低空间消耗的代价就是增加时间。

而现在,硬件技术发展迅速,在强大的硬件支持下,存储空间已经不是困扰人们的问题了。这种情况下,人们要求的是更快的运行速率。再加上现在大数据盛行。牺牲廉价的空间来换取宝贵的时间就很有必要了!

所以,我们现在研究的是:如何用算法有效的降低时间复杂度!

好,现在我们已经了解了复杂度的作用,那应该如何去计算复杂度呢?

二、大O表示法

复杂度是一个关于输入数据量 n 的函数。
假设你的代码复杂度是 f(n),那么就用个大写字母 O 和括号,把 f(n) 括起来就可以了,即 O(f(n))。例如,O(n) 表示的是,复杂度与计算实例的个数 n 线性相关;O(logn) 表示的是,复杂度与计算实例的个数 n 对数相关。
通常,复杂度的计算方法遵循以下几个原则:

  • 首先,复杂度与具体的常系数无关,例如 O(n) 和 O(2n) 表示的是同样的复杂度。我们详细分析下,O(2n) 等于O(n+n),也等于 O(n) + O(n)。也就是说,一段 O(n) 复杂度的代码只是先后执行两遍 O(n),其复杂度是一致的。
  • 其次,多项式级的复杂度相加的时候,选择高者作为结果,例如 O(n²)+O(n) 和 O(n²)表示的是同样的复杂度。具体分析一下就是,O(n²)+O(n) = O(n²+n)。随着 n越来越大,二阶多项式的变化率是要比一阶多项式更大的。因此,只需要通过更大变化率的二阶多项式来表征复杂度就可以了。

值得一提的是,O(1) 也是表示一个特殊复杂度,含义为某个任务通过有限可数的资源即可完成。此处有限可数的具体意义是,与输入数据量 n 无关。

三、时间复杂度计算

  • 常数阶
int i;
    for (i = 0; i < 100; i++){
       / * * /
    }

这里只循环100次。不随着输入数据量 n 增加而增长,所以,此类算法的时间复杂度是O(1)。

  • 线性阶
int i;
    for (i = 0; i < n; i++){
       / * * /
    }

它是一个 for 循环,时间复杂度为 O(n) , 因为循环体中的代码须要执行n次。

  • 对数阶
int count = 1;
while (count < n){
count = count * 2;
/*  */
}

由于每次 count 乘以 2 之后,就距离 n 更近了一分。 也就是说,有多少个 2 相乘后大于 n ,则会退出循环。由 2^x=n 得到x=log2(n) 。 所以这个循环的时间复杂度为O(log(n)) 。

  • 平方阶
int i , j ;
    for (i = 0; i < n; i++){
       for ( j = 0 ; j < n ; j++ ){
            /*  */
        }
    }

而对于外层的循环,不过是内部这个时间复杂度为 O(n)的语句,再循环 n 次 。 所以这段代码的时间复杂度为 O(n^2)。
如果外循环的循环次数改为了m, 时间复杂度就变为 O(m×n)。

int i , j ;
    for (i = 0; i < m; i++){
        for ( j = 0 ; j < n ; j++ ){
            /*  */
         }
    }

所以我们可以总结得出,循环的时间复杂度等于循环体的复杂度乘以该循环运行的次数。

四、常见复杂度的比较

常见的时间复杂度有:
常数阶O(1),
对数阶O(log2 n),
线性阶O(n),
线性对数阶O(n log2 n),
平方阶O(n^2),
立方阶O(n^3)
k次方阶O(n^K),
指数阶O(2^n)。

随着n的不断增大,时间复杂度不断增大,算法花费时间越多。
在这里插入图片描述
为了更好理解,我们来看一些数据。假设某个计算任务需要处理 10万 条数据。你编写的代码:

  • 如果是 O(n²) 的时间复杂度,那么计算的次数就大概是 100 亿次左右。
  • 如果是 O(n),那么计算的次数就是 10万 次左右。
  • 如果这个工程师再厉害一些,能在 O(log n) 的复杂度下完成任务,那么计算的次数就是 17 次左右(log 100000 =16.61,计算机通常是二分法,这里的对数可以以 2 为底去估计)。
  • 而O(1)是固定次数的计算,如:计算任务需要处理 10万 条数据,它是计算100次; 计算任务需要处理 10 条数据,它是还计算100次; 所以并不能和其他O(log n), O(n),O(n²) 混为一谈!
    在这里插入图片描述

五、算法优化的核心方法论

  • 第一步,暴力解法。在没有任何时间、空间约束下,完成代码任务的开发。
  • 第二步,无效操作处理。将代码中的无效计算、无效存储剔除,降低时间或空间复杂度。
  • 第三步,时空转换。设计合理数据结构,完成时间复杂度向空间复杂度的转移。

第一步暴力解法没有太多的套路,只要围绕你面临的问题出发,大胆发挥想象去尝试解决即可。

第二步无效操作处理中,你需要学会并掌握递归、二分法、排序、动态规划等常用的算法思维。通过各种算法将代码中的无效计算(冗余时间)、无效存储(冗余空间)剔除,降低时间或空间复杂度。

第三步时空转换,你需要对数据的操作进行细分,全面掌握常见数据结构的基础知识。再围绕问题,有针对性的设计数据结构、采用合理的算法思维,去不断完成时空转移,用空间换取时间,降低时间复杂度。

六、常见算法复杂度

在这里插入图片描述

算法/排序时间复杂度空间复杂度
线性查找(Linear Search)最好情况 O(1),平均情况 O(n),最坏情况 O(n)O(1)
二分查找(Binary Search)最好情况 O(1),平均情况 O(log n),最坏情况 O(log n)O(1)
冒泡排序(Bubble Sort)最好情况 O(n),平均情况 O(n^2),最坏情况 O(n^2)O(1)
快速排序(Quick Sort)最好情况 O(n log n),平均情况 O(n log n),最坏情况 O(n^2)最好情况 O(log n),平均情况 O(log n),最坏情况 O(n)
堆排序(Heap Sort)最好情况 O(n log n),平均情况 O(n log n),最坏情况 O(n log n)O(1)
插入排序(Insertion Sort)最好情况 O(n),平均情况 O(n^2),最坏情况 O(n^2)O(1)
选择排序(Selection Sort)最好情况 O(n^2),平均情况 O(n^2),最坏情况 O(n^2)O(1)
归并排序(Merge Sort)最好情况 O(n log n),平均情况 O(n log n),最坏情况 O(n log n)O(n)
希尔排序(Shell Sort)最好情况取决于增量序列,平均情况取决于增量序列,最坏情况取决于增量序列O(1)
布隆过滤器(Bloom Filter)插入和查询都是 O(k),其中 k 是哈希函数的数量取决于哈希函数数量和位数组大小
动态规划(Dynamic Programming)根据问题而定,通常在 O(n^2) 到 O(n^3) 之间根据问题而定,通常在 O(n) 到 O(n^2) 之间
KMP 算法(Knuth-Morris-Pratt Algorithm)O(m + n),其中 m 是模式串长度,n 是文本串长度O(m),其中 m 是模式串长度
Prim 算法(Minimum Spanning Tree)O(V^2) 或 O(E log V),V 是顶点数,E 是边数O(V) 或 O(E),取决于具体实现方式
Kruskal 算法(Minimum Spanning Tree)O(E log E),E 是边数O(E)
贪心算法(Greedy Algorithm)根据问题而定,通常在 O(n log n) 到 O(n^2) 之间O(1) 或 O(n),取决于具体实现方式
拓扑排序算法(Topological Sorting)O(V + E),其中 V 是顶点数,E 是边数O(V)
Dijkstra 算法(Shortest Path)O(V^2) 或 O(E log V),V 是顶点数,E 是边数O(V^2) 或 O(VE),取决于具体实现方式
Bellman-Ford 算法(Shortest Path)O(VE),V 是顶点数,E 是边数O(VE)
Floyd-Warshall 算法(Shortest Path)O(V^3),V 是顶点数O(V^2)
Boyer-Moore 算法(String Matching)最坏情况下 O(mn),其中 m 是模式串长度,n 是文本串长度O(m),其中 m 是模式串长度
哈希表(Hash Table)插入、查找、删除操作平均情况下为 O(1),最坏情况下为 O(n)取决于哈希表大小和存储的元素数量
最大子序列和问题(Maximum Subarray Problem)O(n),其中 n 是数组的长度O(1)
括号匹配问题(Parentheses Matching Problem)O(n),其中 n 是字符串的长度O(n)
深度学习算法(Deep Learning Algorithms)取决于神经网络的结构和训练数据量取决于神经网络的参数数量和层数
布隆过滤器(Bloom Filter)插入和查询操作都是 O(k),其中 k 是哈希函数的数量取决于哈希函数数量和位数组大小
Rabin-Karp 字符串匹配算法平均情况下为 O(n + m),最坏情况下为 O(nm)O(1)
RSA 公钥加密算法取决于素数的大小和加密数据的长度O(1)
哈夫曼编码(Huffman Coding)O(n log n),其中 n 是字符集的大小取决于字符集的大小和编码长度
最长公共子序列(Longest Common Subsequence)O(mn),其中 m 和 n 分别是两个字符串的长度O(mn)
最大流算法(Max Flow Algorithm)O(V * E^2),其中 V 是顶点数,E 是边数O(V^2)
最大子数组问题(Maximum Subarray Problem)O(n),其中 n 是数组的长度O(1)
拓扑排序算法(Topological Sorting)O(V + E),其中 V 是顶点数,E 是边数O(V)
最短路径算法(Shortest Path Algorithms)Dijkstra 算法:O(V^2) 或 O(E log V)
Bellman-Ford 算法:O(VE)
Floyd-Warshall 算法:O(V^3)
O(V^2) 或 O(VE)
最小生成树算法(Minimum Spanning Tree Algorithms)Prim 算法:O(V^2) 或 O(E log V)
Kruskal 算法:O(E log E)
O(V) 或 O(E)
贪心算法(Greedy Algorithm)根据问题而定,通常在 O(n log n) 到 O(n^2) 之间O(1) 或 O(n)

五、总结

OK,今天的内容到这儿就结束了。相信你对复杂度的概念有了进一步的认识。总结一下:

程序优化、算法优化、时间复杂度和空间复杂度优化是提高程序性能和效率的关键。程序优化是指通过改进代码结构、算法、数据结构等方面来提高程序的运行速度和资源利用率。算法优化则是针对特定问题设计更高效的算法,以减少计算时间和资源消耗。时间复杂度是衡量算法执行时间的指标,通常用大O符号表示,优化算法可以降低时间复杂度。空间复杂度是衡量算法内存消耗的指标,优化算法可以降低空间复杂度。综上所述,通过程序优化、算法优化、时间复杂度和空间复杂度优化可以使程序更加高效和节约资源。

  1. 程序优化

    • 优化代码结构,消除冗余和重复代码,提高代码可读性和维护性。
    • 使用高效的数据结构和算法,避免不必要的循环和操作。
    • 减少内存分配和释放次数,避免内存泄漏和频繁的内存操作。
  2. 算法优化

    • 选择合适的算法来解决问题,比如排序、查找、图算法等。
    • 优化算法的实现,减少不必要的计算和操作。
    • 考虑特定问题的特点,设计针对性的算法来提高效率。
  3. 时间复杂度优化

    • 分析算法的时间复杂度,选择具有更低时间复杂度的算法。
    • 避免嵌套循环和递归调用,优化算法的执行路径。
    • 尽量减少不必要的计算和操作,提高算法的执行效率。
  4. 空间复杂度优化

    • 分析算法的空间复杂度,选择具有更低空间复杂度的算法。
    • 使用合适的数据结构来减少内存消耗,比如使用数组代替链表。
    • 及时释放不再需要的内存,避免内存泄漏和内存碎片化。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

三菱MR-J4系列伺服驱动器E7.1和32.3故障报警处理总结

三菱MR-J4系列伺服驱动器E7.1和32.3故障报警处理总结 三菱MR-J4系列伺服驱动器出现报警,故障代码为:E7.1和32.3,查阅手册可以看到E7.1和32.3的报警解释信息, 如下图所示,此时简单运动控制模块上的ERROR灯亮, 如下图所示,用GX WORKS3打开备份程序,找到FX5-80SSC-…

一维时间序列信号的广义傅里叶族变换(Matlab)

广义傅里叶族变换是一种时频变换方法&#xff0c;傅里叶变换、短时傅里叶变换、S变换和许多小波变换都是其特殊情况&#xff0c;完整代码及子函数如下&#xff0c;很容易读懂&#xff1a; % Run a demo by creating a signal, transforming it, and plotting the results% Cre…

大数据开发面试题【Mysql篇】

181、mysql数据库中的引擎 用于数据存储、处理和保护数据的核心服务&#xff0c;不同的数据库引擎有其各自的特点&#xff0c;常见的引擎&#xff1a;InnoDB&#xff0c;Mylsam、Memory、Mrg_Mylsam、Blackhole innodb&#xff1a;是一个事务性存储引擎&#xff0c;提供了对事…

Transformer 动画讲解:数据处理的四大关键步骤

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对大模型技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备面试攻略、面试常考点等热门话题进行了深入的讨论。 汇总合集…

MFC工控项目实例之二添加iPlotx控件

承接专栏《MFC工控项目实例之一主菜单制作》 在WIN10下使用Visual C 6.0 &#xff08;完整绿色版&#xff09;添加iPlotx控件的方法。 1、在资源主对话框界面点击鼠标右键如图选择插入Active控件点击进入。 2、选择iPlotx Contrlolh点击确定。 3、在对话框界面插入iPlotx控件。…

NATS-研究学习

NATS-研究学习 文章目录 NATS-研究学习[toc]介绍说明提供的服务内容各模式介绍测试使用发布订阅&#xff08;Publish Subscribe&#xff09;请求响应&#xff08;Request Reply&#xff09;队列订阅&分享工作&#xff08;Queue Subscribers & Sharing Work&#xff09;…

编程入门(七)【虚拟机VMware安装Linux系统Ubuntu】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f525; 欢迎来到我的博客 &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️寻至善的主页 文章目录 &#x1f525;前言&#x1f680;Ubuntu知多少&#x1f680;安装的前期准备&am…

放开了去的 ulimit

放开了去的 ulimit 放开了去的 ulimitulimit简介临时修改打开文件数目永久修改系统总打开句柄限制更多信息 放开了去的 ulimit ulimit简介 对于高并发或者频繁读写文件的应用程序而言&#xff0c;有时可能需要修改系统能够打开的最多文件句柄数&#xff0c;否则就可能会出现t…

3389,为了保障3389端口的安全,我们可以采取的措施

3389端口&#xff0c;作为远程桌面协议&#xff08;RDP&#xff09;的默认端口&#xff0c;广泛应用于Windows操作系统中&#xff0c;以实现远程管理和控制功能。然而&#xff0c;正因为其广泛使用&#xff0c;3389端口也成为许多潜在安全威胁的入口。因此&#xff0c;确保3389…

使用C#实现VS窗体应用——画图板

✅作者简介&#xff1a;大家好&#xff0c;我是 Meteors., 向往着更加简洁高效的代码写法与编程方式&#xff0c;持续分享Java技术内容。&#x1f34e;个人主页&#xff1a;Meteors.的博客&#x1f49e;当前专栏&#xff1a;小项目✨特色专栏&#xff1a; 知识分享&#x1f96d…

ARM32开发——总线与时钟

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 APB总线时钟树时钟树 外部晶振内部晶振 在这个例子中&#xff0c;这条大街和巴士构成了一套系统&#xff0c;我们称之为AHB总线。 …

【数据结构】详解二叉树

文章目录 1.树的结构及概念1.1树的概念1.2树的相关结构概念1.3树的表示1.4树在实际中的应用 2.二叉树的结构及概念2.1二叉树的概念2.2特殊的二叉树2.2.1满二叉树2.2.2完全二叉树 2.3 二叉树的性质2.4二叉树的存储结构2.4.1顺序结构2.4.2链表结构 1.树的结构及概念 1.1树的概念…

乐观锁 or 悲观锁 你怎么选?

你有没有听过这样一句话&#xff1a;悲观者正确&#xff0c;乐观者成功​。那么今天我来分享下什么是乐观锁​和悲观锁。 乐观锁和悲观锁有什么区别&#xff0c;它们什么场景会用 乐观锁 乐观锁基于这样的假设&#xff1a;多个事务在同一时间对同一数据对象进行操作的可能性很…

三体中的冯诺依曼

你叫冯诺依曼&#xff0c;是一位科学家。你无法形容眼前的现态&#xff0c;你不知道下一次自己葬身火海会是多久&#xff0c;你也不知道会不会下一秒就会被冰封&#xff0c;你唯一知道的&#xff0c;就是自己那寥寥无几的科学知识&#xff0c;你可能会抱着他们终身&#xff0c;…

基于Android Studio记事本系统

目录 项目介绍 图片展示 运行环境 获取方式 项目介绍 具有登录&#xff0c;注册&#xff0c;记住密码&#xff0c;自动登录的功能&#xff1b; 可以新增记事本&#xff0c;编辑&#xff0c;删除记事本信息&#xff0c;同时可以设置主标题&#xff0c;内容&#xff0c;以及…

SpringBoot【1】集成 Druid

SpringBoot 集成 Druid 前言创建项目修改 pom.xml 文件添加配置文件开发 java 代码启动类 - DruidApplication配置文件-propertiesDruidConfigPropertyDruidMonitorProperty 配置文件-configDruidConfig 控制层DruidController 运行验证Druid 的监控应用程序 前言 JDK版本&…

【HarmonyOS - ArkTS - 状态管理】

概述 本文主要是从页面和应用级两个方面介绍了ArkTS中的状态管理的一些概念以及如何使用。可能本文比较粗略&#xff0c;细节化请前往官网(【状态管理】)学习&#xff0c;若有理解错误的地方&#xff0c;欢迎评论指正。 装饰器总览 由于下面会频繁提及到装饰器&#xff0c;所…

【CH32V305FBP6】调试入坑指南

1. 无法烧录程序 现象 MounRiver Studio WXH-LinkUtility 解决方法 前提&#xff1a;连接复位引脚 或者 2. 无法调试 main.c 与调试口冲突&#xff0c;注释后调试 // USART_Printf_Init(115200);

orin部署tensorrt、cuda、cudnn、pytorch、onnx

绝大部分参考https://blog.csdn.net/qq_41336087/article/details/129661850 非orin可以参考https://blog.csdn.net/JineD/article/details/131201121 报错显卡驱动安装535没法安装、原始是和l4t-cuda的部分文件冲突 Options marked [*] produce a lot of output - pipe it t…

三方语言中调用, Go Energy GUI编译的dll动态链接库CEF

如何在其它编程语言中调用energy编译的dll动态链接库&#xff0c;以使用CEF 或 LCL库 Energy是Go语言基于LCL CEF开发的跨平台GUI框架, 具有很容易使用CEF 和 LCL控件库 interface 便利 示例链接 正文 为方便起见使用 python 调用 go energy 编译的dll 准备 系统&#x…