复杂度(上卷)

前言

在正式进入今天的主题之前,我们不妨先来回顾一下初步学习数据结构后必须知道的概念。🎶

数据结构

数据结构是计算机存储、组织数据的方式,指相互间存在一种或多种特定关系的数据元素的集合

(没有一种单一的数据结构能够满足所有用途,因此我们需要学习各种不同的数据结构。如:线性表、树、图、哈希等。)

算法

算法(Algorithm)指的是定义好的计算过程,它取一个或一组的值为输入,并产生出一个或一组值作为输出

简单来说算法就是一系列的计算步骤,用于将输入数据转化成输出结果。


正文

我们知道算法有好坏之分,但是要怎么去衡量一个算法的好坏呢?

比如我们在一些刷题平台做算法题时会遇到“超出时间限制”的“滑铁卢”,所以我们可以知道,时间就是其中一个标准。除此之外还有空间。用最少的时间办最大的事是我们现实中办事能力好坏的标准,对于算法来说也是类似的:用最少的时间、最少的空间,解决问题,这就是好的算法。

其实我们有一个专门衡量算法好坏的概念:复杂度。复杂度分为时间复杂度和空间复杂度,刚好从两个维度去衡量一个算法。

说得更精确一些:时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间

其实在计算机发展的早期,计算机的存储容量很小所以对空间复杂度很是在乎。而如今计算机的存储容量不可同日而语,所以我们已不再特别关注空间复杂度而是以时间复杂度为主

小小补充:摩尔定律

摩尔定律最初是由英特尔公司的创始人之一戈登·摩尔在1965年提出的观察结果和预测。他发现集成电路上可容纳的晶体管数量每隔一段时间就会翻倍,同时价格也会减半。这一发现揭示了信息技术进步的速度,并成为了计算机行业的基础法则之一。

摩尔定律的实际效果是,计算机芯片的性能每隔一段时间就会显著提高,而成本也会随之下降。这一定律对整个信息技术产业的发展产生了深远的影响,推动了半导体芯片的集成化趋势,进而给人们的生活带来了巨大的变化。然而,随着晶体管电路逐渐接近性能极限,摩尔定律的适用性也面临挑战。

归纳起来,“摩尔定律”主要有以下3种“版本”:

  1. 集成电路芯片上所集成的电路的数目,每隔18个月就翻一番;
  2. 微处理器的性能每隔18个月提高一倍,而价格下降一半;
  3. 用一美元所能买到的计算机性能,每隔18个月翻两番。

这里不再多阐述,回到复杂度。

时间复杂度

在计算机科学中,算法的时间复杂度是一个函数式T(N),它定量描述了该算法的运行时间。

这里的函数不是我们C语言中指的函数,而是数学概念。

先来说说,时间复杂度是衡量程序的时间效率,那么为什么不去计算程序的运行时间呢?

我们是可以计算程序的运行时间的。我们可以分别计算程序开始和结束的时间进行相减得到运行时间。

如:

#include<stdio.h>
#include<time.h>int main()
{//计算程序运行时间int begin = clock();//用来保存程序的运行时间int count = 0;for (int i = 0; i < 1000000000; i++){count++;}int end = clock();printf("time:%d\n", end - begin);return 0;
}

执行后我的电脑上VS(Debug模式)得到的结果是time:507,Release模式则是0。单位是毫秒。

而且多次运行得出来的结果还不一定相同,无法给出精确的运行时间。

  1. 程序运行时间和编译环境以及运行机器的配置都有关系,同一个算法程序,用一个老编译器和新编译器进行编译,在同样机器下运行时间不同。

  2. 同一个算法程序,用一个老低配置机器和新高配置机器,运行时间也不同。

  3. 并且运行的时间只能程序写好后测试,不能在编写程序之前通过理论思想计算评估。(而我们希望算法的复杂度是我们在编写程序之前就能知道的。)

这几点解释了为什么通过计算程序的运行时间来评估算法的时间效率不好。

时间复杂度和空间复杂度是我们在提出一个算法,编写程序之前就能知道的,是一个粗估而非精确的结果。

时间复杂度该怎么计算呢?

程序的时间效率由两个维度决定:每条语句运行的时间和运行的次数。

程序时间效率:每条语句运行时间×运行次数

每条语句运行的时间也如前面所说,和编译环境、运行机器的配置等有关,不好确定。但是运行的次数我们是可以知道的。比如上面的int count = 0;这句代码的运行次数为1,而count++;的运行次数为1000000000。

我们可以将每条语句运行时间去掉,只看运行次数。

为什么可以这样呢?

在这里插入图片描述

因为程序的运行时间和运行次数是正相关的,所以我们可以不看每条语句运行时间,通过运行次数来看时间复杂度。

计算时间复杂度的案例

我们现在有一道算法题,来看看怎么计算它的时间复杂度。

// 请计算⼀下Func1中++count语句总共执⾏了多少次? 
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; } 
}

我们看的是运行次数。在第一个嵌套循环中:外循环每执行1次,内循环要执行N次,而外循环执行了N次。所以整个部分执行了N×N次;在下一个循环中执行了2×N次;最后一个循环执行了10次。所以该算法的时间复杂度函数式为T(N)=N²+2N+10。

定义变量这样的语句相较于其他的循环语句运行次数太小,故忽略不计。

我们可以看出N的大小决定了T(N)的大小:

NT(N)
10130100
1001021010000
100010020101000000

可以看出对T(N)影响大的是N²,而2N+10对结果的影响小,可以忽略不计,只看影响最大的项。T(N)=N²,是一个粗估。

大O的渐进表示法

上面我们讲了T(N),但其实复杂度的表示我们使用的是大O的渐进表示法。

比如上面的那个算法的时间复杂度就表示为O(N²)。

大O既可以表示时间复杂度,也可以表示空间复杂度。

接下来我们来看本文中最重要的内容:

推导大O阶规则

  1. 时间复杂度函数式T(N)中,只保留最⾼阶项,去掉那些低阶项,因为当N不断变⼤时, 低阶项对结果影响越来越⼩,当N⽆穷⼤时,就可以忽略不计了。
  2. 如果最⾼阶项存在且不是1,则去除这个项⽬的常数系数,因为当N不断变⼤,这个系数 对结果影响越来越⼩,当N⽆穷⼤时,就可以忽略不计了。
  3. T(N)中如果没有N相关的项⽬,只有常数项,⽤常数1取代所有加法常数。

可以看到,刚才那个算法的例子就是符合以上规则的一个例子。

我们现在再来看几个例子。

示例1
// 计算Func2的时间复杂度? 
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。所以T(N)=2N+10

根据规则1,10作为低阶项,被去掉;

根据规则2,最⾼阶项存在且不是1,去除这个项⽬的系数2。

所以最后我们得到的时间复杂度为O(N)。

对于计算机来说,1万和2万,1亿和2亿的区别并不大,所以可以去除系数。2倍的无穷大与无穷大结果一样。

示例2
// 计算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); 
}

第一个循环的执行次数为M,第二个循环执行次数为N,所以T(N)=M+N,M和N都是变量,都会影响T(N)的大小。那么时间复杂度是多少呢?答案是O(M+N)。

如果T(N)=2M+N,也是O(M+N),没有区别。因为M和N取极限时,2倍的极限和极限没有区别。

大小关系复杂度
M>>NO(M)
M<<NO(N)
M==NO(M+N)

注意:

  • 这里的高阶项与低阶项如何区别?高和低是相对而言的,看谁对结果影响最大则为高阶。

  • 一亿这样的数对于我们来说虽然感觉很大,对于高速处理数据的计算机来说却不过是一秒不到的事,所以我们说数字大与小是要对于计算机来说而不是我们的主观感受。当我们说“非常大”,指的是无限。

本文到此结束,但对于复杂度的探讨并没有结束,敬请期待下卷。( ̄︶ ̄)↗

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

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

相关文章

ServiceDesk Plus再次获得国际认可的粉象认证

我们又一次做到了&#xff01;ServiceDesk Plus 现已获得 CMDB 和发布部署过程的 PinkVERIFY™ &#xff08;粉象&#xff09;认证。 通过PinkVerify 认证&#xff0c;我们现在已经获得了七项核心 IT 服务管理实践&#xff1a; 1、事件管理 2、问题管理 3、变更管理 4、资产管…

探索 Electron:窗口菜单以及生命周期和对话框讲解

Electron是一个开源的桌面应用程序开发框架&#xff0c;它允许开发者使用Web技术&#xff08;如 HTML、CSS 和 JavaScript&#xff09;构建跨平台的桌面应用程序&#xff0c;它的出现极大地简化了桌面应用程序的开发流程&#xff0c;让更多的开发者能够利用已有的 Web 开发技能…

Python类与对象01

1、理解使用对象完成数据组织的思路 1.1类和对象的基本理解 理解类&#xff1a;从现实世界到编程世界 类由三个部分组成&#xff1a;类名、类的属性、类的方法。类的定义实际上是描述事物的一种方法&#xff0c;在现实世界中&#xff0c;事物都是有属性和行为的。通过类&…

JVM:类加载器

文章目录 一、什么是类加载器二、类加载器的应用场景三、类加载器的分类1、分类2、启动类加载器 四、双亲委派机制五、打破双亲委派机制六、JDK9之后的类加载器 一、什么是类加载器 类加载器&#xff08;ClassLoader&#xff09;是Java虚拟机提供给应用程序去实现获取类和接口…

休息时间c++

题目描述 小杨计划在某个时刻开始学习&#xff0c;并决定在学习k秒后开始休息。 小杨想知道自己开始休息的时刻是多少。 输入 前三行每行包含一个整数&#xff0c;分别表示小杨开始学习时刻的时h、分m、秒s(h&#xff0c;m&#xff0c;s的值符合1≤h≤12,0≤m≤59,0≤s≤59)…

Geoserver源码解读六 插件

系列文章目录 Geoserver源码解读一 环境搭建 Geoserver源码解读二 主入口 Geoserver源码解读三 GeoServerBasePage Geoserver源码解读四 REST服务 Geoserver源码解读五 Catalog Geoserver源码解读六 插件&#xff08;怎么在开发模式下使用&#xff09; 目录 系列文章目…

看番工具 -- oneAnime v1.2.5绿色版

软件简介 OneAnime是一款专为动漫爱好者设计的应用程序&#xff0c;它提供了一个庞大的动漫资源库&#xff0c;用户可以在这里找到各种类型的动漫&#xff0c;包括热门的、经典的、新番的等等。OneAnime的界面设计简洁明了&#xff0c;操作方便&#xff0c;用户可以轻松地搜索…

C++系列-Vector(一)

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” Vector的介绍及使用 Vector的介绍 当vector构建的参数类型为char类型时&#xff0c;它是和string是极其类似的&#xff0c;但是二者之间也有不同&#xff0c;比如&#xff0c…

[C++] 模拟实现list(二)

标题&#xff1a;[C] 模拟实现list&#xff08;二&#xff09; 水墨不写bug 目录 &#xff08;一&#xff09;回顾 &#xff08;二&#xff09;迭代器类的封装设计 &#xff08;1&#xff09;成员函数简要分析 &#xff08;2&#xff09;const迭代器类的设计 &#xff08;…

二四、3d人脸构建

一、下载github项目3dmm_cnn-master https://github.com/anhttran/3dmm_cnn.git 一个使用深度神经网络从单个图像进行 3D 人脸建模的项目,端到端代码,可直接根据图像强度进行 3D 形状和纹理估计;使用回归的 3D 面部模型,从检测到的面部特征点估计头部姿势和表情。…

19185 01背包问题

解决这个问题的关键是使用动态规划的方法。我们可以创建一个二维数组dp[i][j]&#xff0c;其中i表示考虑前i件物品&#xff0c;j表示背包的容量。dp[i][j]的值表示在考虑前i件物品&#xff0c;且背包容量为j时能获得的最大价值。 ### 算法步骤 1. 初始化一个二维数组dp&#x…

机器学习(五) -- 监督学习(7) --SVM2

系列文章目录及链接 上篇&#xff1a;机器学习&#xff08;五&#xff09; -- 监督学习&#xff08;7&#xff09; --SVM1 下篇&#xff1a; 前言 tips&#xff1a;标题前有“***”的内容为补充内容&#xff0c;是给好奇心重的宝宝看的&#xff0c;可自行跳过。文章内容被“文…

ABAQUS大连正版代理商:亿达四方——开启东北工业智能仿真新篇章

在东北老工业基地的振兴道路上&#xff0c;大连以其独特的地理位置和深厚的产业基础&#xff0c;成为推动区域经济发展的领头羊。作为国际知名的仿真软件ABAQUS在大连地区的官方授权代理商&#xff0c;亿达四方正以科技创新为驱动&#xff0c;引领当地制造业迈向数字化、智能化…

SD卡讲解

SD 卡 (Secure Digital Memory Card) 在我们生活中已经非常普遍了&#xff0c;控制器对 SD 卡进行读写通信 操作一般有两种通信接口可选&#xff0c;一种是 SPI 接口&#xff0c;另外一种就是 SDIO 接口。SDIO 全称是安全数 字输入/输出接口&#xff0c;多媒体卡 (MMC)、SD 卡、…

【Python实战因果推断】30_双重差分1

目录 Panel Data 在讨论了干预效果异质性之后&#xff0c;是时候转换一下思路&#xff0c;回到平均干预效果上来了。在接下来的几章中&#xff0c;您将学习如何利用面板数据进行因果推断。 面板数据是一种跨时间重复观测的数据结构。在多个时间段观察同一单位&#xff0c;可以…

构建实时银行应用程序:英国金融机构 Nationwide 为何选择 MongoDB Atlas

Nationwide Building Society 超过135年的互助合作 Nationwide Building Society&#xff08;以下简称“Nationwide”&#xff09; 是一家英国金融服务提供商&#xff0c;拥有超过 1500 万名会员&#xff0c;是全球最大的建房互助会。 Nationwide 的故事可以追溯到 1884 年&am…

web后端开发--请求响应

目录 前言 请求 简单参数 原始方法 Spring方式 Post请求乱码处理 实体参数 简单实体参数 复杂实体参数 ​编辑 数组集合参数 数组参数 ​编辑 集合参数 日期参数 ​编辑 Json参数 ​编辑 传递json数据 json数组 json对象&#xff08;POJO&#xff09; jso…

Dify中的知识库API列表

1.知识库API列表 通过文本/文件创建/更新/删除文档/查询文档嵌入状态&#xff0c;知识库创建/知识库查询/文档列表查询&#xff0c;分段增/删/改/查。 接口名字功能描述请求示例POST/datasets/{dataset_id}/document/create_by_text通过文本创建文档此接口基于已存在知识库&a…

tableau人口金字塔,漏斗图,箱线图绘制 - 13

人口金字塔&#xff0c;漏斗图&#xff0c;箱线图 1. 金字塔1.1 定义1.2 金字塔创建1.2.1 数据导入1.2.2 数据异常排查1.2.3 创建度量字段1.2.4 转换属性1.2.5 创建数据桶1.2.6 选择相关属性1.2.7 年龄排序1.2.8 创建计算字段1.2.9 选择相关字段1.2.10 设置轴排序1.2.11 设置颜…

liunx清理服务器内存和日志

1、查看服务器磁盘占用情况 # 查看磁盘占用大小 df -h 2、删除data文件夹下面的日志 3、查看每个服务下面的日志输出文件&#xff0c;过大就先停掉服务再删除out文件再重启服务 4、先进入想删除输入日志的服务文件夹下&#xff0c;查看服务进程&#xff0c;杀掉进程&#xff…