算法基础——复杂度
前言
算法是解决问题的一系列操作的集合。著名的计算机科学家Niklaus Wirth曾提出:算法+数据结构=程序,由此可见算法在编程中的重要地位。本篇主要讨论算法性能好坏的标准之一——复杂度。
1 复杂度概述
1.1 什么是复杂度
本文所讨论的复杂度是指通过事先估算法计算出的用来衡量算法效率的值。因此并不是说代码的长度越长复杂度就越高,或者代码中的数据越多复杂度就越高。
1.2 为什么会有复杂度
关于复杂度的产生,我们通过下图的问题来引入。假设现在有一个人要从A地前往E地,那么他共有ABE、ADE、ABCE三种路径可以选择,但是我们可以看出,ABE的路程是最短的(假设图中距离与实际距离成比例),经过的地区也是最少的,相较于其它两条路径,ABE的优势非常明显。
算法是用来解决问题的操作步骤的集合,而我们解决同一个问题可能会如上例一样,有多种路径,这时我们需要一个标准来评判每条路径的好坏。这样一来,算法的复杂度就被提出了。
1.3 复杂度分类
算法的复杂度分为时间复杂度和空间复杂度两种。
时间复杂度用来衡量算法消耗时间的多少,空间复杂度用来衡量算法所需开辟新空间的多少。
2 时间复杂度
2.1 什么是时间复杂度
首先我们要明白,程序运行的绝对时间并不是衡量算法效率的标准。举个不太恰当的例子,使用“ENIAC”计算机(世界上第一台电子数字式计算机)与“天河一号”超级计算机执行相同的算法(死循环除外),它们的绝对时间必然有所不同。
从中我们得出,当我们评判一个算法的好坏时,不能附带有硬件条件的影响,而应关注算法本身。算法的执行时间的长度可以由每条语句的执行时间乘以该语句的执行次数,再加和得到。而每条语句的单次执行时间对现代计算机来说几乎可以忽略不计,那么对于一个程序的执行时间影响最大的因素就是单条语句的执行次数了。
因此,时间复杂度主要是用来渐进表示执行次数最多的单条语句的执行次数。
2.2 时间复杂度的计算
时间复杂度通常用"O"表示。对于任意输入量n(这里的输入量不一定是数字,也可能为数组或结构变量等,根据具体情况而定),算法的时间复杂度可记作"O(f(n))",其中f(n)为n的函数,表示算法中的语句的执行次数。但是我们通常会使用渐进表示法来表示时间复杂度,即取f(n)中次数最高的项来表示(当n足够大时,最高次项对整个数值的影响最大)。
如果仅仅通过这些文字来理解时间复杂度可能较为抽象,我们通过几个简单的例子来理解一下。
int main()
{int a;int i;for(i=0;i<10;i++)a=i;return 0;
}
我们来看这段代码,这其中代码执行的总次数(即执行的语句数量)为13,为一个常数,我们计这段代码的时间复杂度为"O(1)"。
这可能与大家的理解有些偏差,明明执行次数为13,为什么不计作"O(13)"呢?我们来观察这段代码,由于这段代码的输入量n对程序的运行次数不会产生影响,那么无论输入量n的大小为多少,这段代码的执行次数都是13。当输入量n趋向于无穷大时,执行次数13与1的区别并不大,可以忽略不计。为方便起见,将这些执行次数为常数的算法的时间复杂度计为"O(1)"。
也就是说,无论算法的语句执行次数为10,20,30还是一亿,十亿,一百亿,只要它是常数,那么当输入量n趋向于无穷大时,这些常数均可忽略不计,则时间复杂度计为"O(1)"。
int main()
{int a;int n;int i;scanf("%d",&n);for(i=0;i<n;i++)a=i;return 0;
}
我们再来看这段代码,这段代码与之前的代码相比的不同之处在于其有一个输入量n,并且输入量n会对代码的执行次数产生影响,例如,当输入2时,代码执行次数为7;输入量为10时,代码执行次数为15。可以推出,这段代码的执行次数与输入量n的关系为f(n)=n+5。
在这个关系中,当n趋向于无穷大时,5这个常量可忽略不计,因此我们将这个算法的时间复杂度计为"O(n)"。
通过对上面两段代码的分析,我们可以总结出一些规律,对比可以发现,影响时间复杂度的主要因素就是循环语句的循环次数与输入量n的关系。在第二段代码中,输入量为n时,循环语句执行n次,则时间复杂度为"O(n)"。
int main()
{int i,j,n;scanf("%d",&n);for(i=0;i<n;i++){for(j=0;j<n;j++){printf("%d ",i*j);}}while(i--){j--;}return 0;
}
我们继续来看,这段代码中存在嵌套循环语句,当输入量为n时,对于任意一次外层循环,内层循环都进行n次。而外层循环也进行n次,因此总共可认为进行n*n次,即n^2。
除此之外,这段代码中还存在另一段while循环语句,不难得出循环进行n次,因此这段代码执行的总次数可以计为n^2+n+2。当输入量n趋向于无穷大时,相对于n^2,n+2的值对总次数的影响并不大,可以忽略不计。
因此,这段代码的时间复杂度计为"O(n^2)"。
2.3 一些经典算法的时间复杂度分析
2.3.1 冒泡排序
冒泡排序是一种简单的交换排序,通过比较相邻两个元素的大小,若它们的大小顺序与所需顺序不同,则交换这两个元素。这里先不讨论冒泡排序的原理,直接来分析代码。
void BubbleSort(int* a,int sz)
{int i,j;for(i=0;i<sz-1;i++) //排序次数{for(j=0;j<sz-i-1;j++) //比较次数{if(arr[j]<arr[j+1]) //判断相邻两数据大小{int tmp = arr[j];arr[j] = arr[j+1];arr[j+1] = tmp;}}}
}
上面为一段最简单的冒泡排序算法,其中参数a为所需排序的数组的地址,sz为所需排序的元素个数,当给定数组大小为n时,该算法的循环总共进行[(n-1)+(n-2)+...+1]次,化简可得(n^2-n)/2次,因此该算法的时间复杂度计为"O(n^2)"。
2.3.2 二分查找
二分查找是一种非常高效的查找算法,可以在一串有序数据中快速查找出所需数据。
int BinarySearch(int* a,int sz,int x)
{int left=0,right = sz-1;while(left<=right){int mid = (left+right)/2; //取中间值if(a[mid] == x) return mid; //找到与x相等的元素,返回下标else if(x > a[mid])left = mid; //中间值偏小elseright = mid; //中间值偏大}return -1; //查找失败
}
二分查找算法的最好情况是第一次就找到目标值,此时时间复杂度为"O(1)"。但是我们评判一个算法的好坏不可能凭其处理问题的最好情况时的复杂度作为参考,这是没有意义的。一般来讲,我们会以算法的处理最坏情况的时间复杂度作为该算法的时间复杂度。
在最坏的情况下,在整个数组中无法找到目标元素。由于二分查找每次取中间值即可排除一半的元素,因此可得当输入量为n时,二分查找总共需要进行大约为以二为底n的对数次,当对数的底数为2时,在表示算法的时间复杂度时,可计为"O(logn)"。
因此,二分查找的时间复杂度为"O(logn)"。
3 空间复杂度
3.1 什么是空间复杂度
我们知道,程序运行时需要调用内存空间,而算法作为程序的一部分,同样也需要调用空间。
而空间复杂度就是衡量一个算法执行所需调用空间的大小的标准。
在很多情况下,在评判算法的性能好坏时,空间复杂度的参考权重比时间复杂度要低得多,因此有些算法可能会采取牺牲空间复杂度的方式来换取更低的时间复杂度。但这并不代表空间复杂度不重要。
3.2 空间复杂度的计算
空间复杂度的计算和时间复杂度的计算较为相似,在理解了时间复杂度的计算规则后,理解空间复杂度的计算应该会比较轻松。我们还是通过例子来分析。
int main()
{int a;return 0;
}
上面这段代码可以说非常简单了,总共开辟了两块内存空间,一块用来调用main函数,另一块存储变量a。由于开辟的空间大小为常数,因此该段代码的空间复杂度为"O(1)"。
int* copy(int* a,int sz)
{int* ret = (int*)malloc(sizeof(int)*sz);return ret;
}
这是一个复制函数,用来将给定的数组复制。在这个函数中,当给定数组的大小为n时,该函数会开辟一个大小为n的空间用来复制数组,因此其空间复杂度为"O(n)"。
结束语
了解复杂度是学习算法的基础,它让我们明白如何判断一个算法的好坏。学会判断复杂度后,我们将会知道如何优化算法,并学会在不同情景下使用最合适的算法。
以上就是我对复杂度的理解了,如内容有误欢迎各位大佬指正。
相关文章:

算法基础——复杂度
前言 算法是解决问题的一系列操作的集合。著名的计算机科学家Niklaus Wirth曾提出:算法数据结构程序,由此可见算法在编程中的重要地位。本篇主要讨论算法性能好坏的标准之一——复杂度。 1 复杂度概述 1.1 什么是复杂度 本文所讨论的复杂度是指通过事先…...

基类与派生类对象的关系 派生类的构造函数
🐶博主主页:ᰔᩚ. 一怀明月ꦿ ❤️🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C 🔥座右铭:“不要等到什么都没有了,才下…...

【算法】生成分布式 ID 的雪花算法
ID 是数据的唯一、不变且不重复的标识,在查询数据库的数据时必须通过 ID 查询,在分布式环境下生成全局唯一的 ID 是一个重要问题。 雪花算法(snowflake)是一种生成分布式环境下全局唯一 ID 的算法,该算法由 Twitter 发…...

Linux系统编程 - 基础IO(IO操作)
目录 预备知识 复习C文件IO相关操作 printf相关函数 fprintf snprintf 读取文件 系统文件IO操作 open函数 umask()函数 open函数返回值 预备知识 1.你真的理解文件原理和操作了吗?不是语言问题,是系统问题2.是不是只有C/C有文件操作呢&#x…...
基于 Avue 的 CRUD 表格组件封装
在 components 文件夹中,创建一个新的 .vue 文件,例如:AvueCrudTable.vue。 透传父组件传递的属性和事件 : 1、利用v-bind=“ a t t r s " 支持所有 a v u e 的使用方法并在其基础上进行封装 2 、使用 v − o n = " attrs"支持所有 avue 的使用方法并在其基…...
树莓派学习笔记(十三)基于框架编写驱动代码
文章目录一、代码分析:二、源码一、代码分析: 在内核中由于代码文件多,避免函数名重复,使用static将函数的作用域限制在该文件内 内核的打印函数printk和printf类似 file_operations结构体使用符号“ . ”指定参数,省…...

vue事件修饰符之.prevent
.prevent 事件修饰符只是阻止默认事件,不会自动触发任何事件处理函数。因此,在使用 .prevent 事件修饰符时,需要自己编写相应的事件处理函数来处理事件。 例如,在上面的例子中,我们通过在表单上绑定 submit.prevent&q…...

【SpringCloud AlibabaSentinel实现熔断与限流】
本笔记内容为尚硅谷SpringCloud AlibabaSentinel部分 目录 一、Sentinel 1、官网 2、Sentinel是什么 3、下载 4、特性 5、使用 二、安装Sentinel控制台 1、sentinel组件由2部分构成 2、安装步骤 1.下载 2.运行命令 3.访问sentinel管理界面 三、初始化演示工程 …...

类与对象-封装
一、封装的意义封装是C面向对象三大特性之一语法: class name { 访问权限:属性行为 };注意:类中的属性和行为 统称为成员属性 又称 成员属性 / 成员变量行为 又称 成员函数 / 成员方法封装将属性和行为作为一个整体,表现生活中的事物例①&…...

【回忆杀】2012年拥有第一台电脑【致逝去的青春】
高中说起 在2012年的时候吧,高考过后,那个时候一门心思的想当一名体育老师【现在居然还有这个想法,哈哈】,最后没有考上自己希望的大学我记得好像是2012年7月的时候就去重庆投靠朋友,他教我做模具,2012年做…...

PointNeXt: Revisiting PointNet++ with Improved Training and Scaling Strategies
Abstract PointNet 是点云理解领域最有影响力的神经网络架构之一。虽然近期出现了 PointMLP 和 Point Transformer 等新型网络,它们的精度已经大大超过了 PointNet,但我们发现大部分性能提升是由于改进的训练策略,例如数据增强和优化技术以及…...

打印九九乘法表-课后程序(JavaScript前端开发案例教程-黑马程序员编著-第2章-课后作业)
【案例2-9】打印九九乘法表 一、案例描述 考核知识点 for双重循环 练习目标 掌握for循环应用。实现九九乘法表。 需求分析 九九乘法表相信大家一点也不陌生,之前见到的乘法表是印刷在课程本之上的。而在本案例中我们将用JavaScript代码来实现九九乘法表。 案例分…...

【Linux】基于阻塞队列的生产者消费者模型
🌠 作者:阿亮joy. 🎆专栏:《学会Linux》 🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根 目录👉为何要使用…...
【华为OD机试 2023最新 】 真正的密码(C++)
文章目录 题目描述输入描述输出描述用例题目解析C++题目描述 在一行中输入一个字符串数组,如果其中一个字符串的所有以索引0开头的子串在数组中都有,那么这个字符串就是潜在密码, 在所有潜在密码中最长的是真正的密码,如果有多个长度相同的真正的密码,那么取字典序最大的…...
差分算法(蓝桥杯复习+例题讲解+模板c++)
文章目录差分介绍差分应用区间加区间求和总结3729. 改变数组元素100. 增减序列文章首发于:My Blog 欢迎大佬们前来逛逛 差分介绍 差分是一种常见的算法,用于快速修改数组中某一段区间的值。 差分的思想就是预处理出数组的差分数组,然后修改…...

CSS+ JS 实现手电筒效果
前言概述 JavaScript 结合 CSS 打造的一款图片特效,当鼠标拖拽滑块时,让本该置灰的图片局部恢复本来的颜色。且该效果随着你的鼠标的按下时的移动而移动。 核心功能 图片置灰 拖拽功能 让滑块位置处的图片恢复本来的颜色 实现原理 这个的实现原理并不…...

2021地理设计组二等奖:基于InSAR和指数分析的地面沉降风
作品简介 一、作品背景 地面沉降是指地面高程的降低, 又称地面下沉或地沉, 是以缓慢、难以察觉的向下垂直运动为主, 是指在自然和人为因素作用下, 由于地壳表层土体压缩而导致区域性地面标高降低的一种环境现象。目前, 地面沉降己成为城市化进程中普遍存在的生态环境问题, 成为…...

计算机操作系统(第四版)第二章进程的描述与控制—课后习题答案
1.什么是前趋图?为什么要引入前趋图? 前趋图是一个有向无循环图,记为DAG,用于描述进程之间执行的先后关系。 2.试画出下面四条语句的前趋图: S1:axy; S2:bz1; S3:ca-b; S4:wc1; 3.为什么程序并发执行会产生间断性特征&…...

CAN通信----电路图
CAN通信----基本原理 一、CAN总线网络连接 1.闭环总线网络----ISO11898 闭环总线网络高速、短距离,它的总线最大长度为 40m,通信速度最高为 1Mbps,总线的两端各要求有一个120 欧的电阻。 2.开环总线网络----ISO11519 开环总线网络低速、…...

Windows系统安装ElasticSearch(一)
一 ES介绍Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene(TM) 基础上的搜索引擎.当然 Elasticsearch 并不仅仅是 Lucene 那么简单,它不仅包括了全文搜索功能,还可以进行以下工作:分布式实时文件存储&#…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...

聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...

push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...